Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ZRANGEBYLEX command synonym. #985

Merged
merged 4 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions libs/resources/RespCommandsDocs.json
Original file line number Diff line number Diff line change
Expand Up @@ -7583,6 +7583,57 @@
}
]
},
{
"Command": "ZRANGEBYLEX",
"Name": "ZRANGEBYLEX",
"Summary": "Returns the number of members in a sorted set within a lexicographical range.",
"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 \u0060BYLEX\u0060 argument",
"Arguments": [
{
"TypeDiscriminator": "RespCommandKeyArgument",
"Name": "KEY",
"DisplayText": "key",
"Type": "Key",
"KeySpecIndex": 0
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "MIN",
"DisplayText": "min",
"Type": "Double"
},
{
"TypeDiscriminator": "RespCommandBasicArgument",
"Name": "MAX",
"DisplayText": "max",
"Type": "Double"
},
{
"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": "ZRANGEBYSCORE",
"Name": "ZRANGEBYSCORE",
Expand Down
25 changes: 25 additions & 0 deletions libs/resources/RespCommandsInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -5179,6 +5179,31 @@
}
]
},
{
"Command": "ZRANGEBYLEX",
"Name": "ZRANGEBYLEX",
"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": "ZRANGEBYSCORE",
"Name": "ZRANGEBYSCORE",
Expand Down
2 changes: 2 additions & 0 deletions libs/server/Objects/SortedSet/SortedSetObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum SortedSetOperation : byte
ZINCRBY,
ZRANK,
ZRANGE,
ZRANGEBYLEX,
ZRANGEBYSCORE,
ZRANGESTORE,
GEOADD,
Expand Down Expand Up @@ -262,6 +263,7 @@ public override unsafe bool Operate(ref ObjectInput input, ref GarnetObjectStore
case SortedSetOperation.ZRANGESTORE:
SortedSetRange(ref input, ref output.SpanByteAndMemory);
break;
case SortedSetOperation.ZRANGEBYLEX:
case SortedSetOperation.ZRANGEBYSCORE:
SortedSetRange(ref input, ref output.SpanByteAndMemory);
break;
Expand Down
3 changes: 3 additions & 0 deletions libs/server/Objects/SortedSet/SortedSetObjectImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,9 @@ private void SortedSetRange(ref ObjectInput input, ref SpanByteAndMemory output)
ZRangeOptions options = new();
switch (input.header.SortedSetOp)
{
case SortedSetOperation.ZRANGEBYLEX:
options.ByLex = true;
break;
case SortedSetOperation.ZRANGESTORE:
options.WithScores = true;
break;
Expand Down
1 change: 1 addition & 0 deletions libs/server/Resp/Objects/SortedSetCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ private unsafe bool SortedSetRange<TGarnetApi>(RespCommand command, ref TGarnetA
{
RespCommand.ZRANGE => SortedSetOperation.ZRANGE,
RespCommand.ZREVRANGE => SortedSetOperation.ZREVRANGE,
RespCommand.ZRANGEBYLEX => SortedSetOperation.ZRANGEBYLEX,
RespCommand.ZRANGEBYSCORE => SortedSetOperation.ZRANGEBYSCORE,
RespCommand.ZREVRANGEBYLEX => SortedSetOperation.ZREVRANGEBYLEX,
RespCommand.ZREVRANGEBYSCORE => SortedSetOperation.ZREVRANGEBYSCORE,
Expand Down
5 changes: 5 additions & 0 deletions libs/server/Resp/Parser/RespCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public enum RespCommand : ushort
ZMSCORE,
ZRANDMEMBER,
ZRANGE,
ZRANGEBYLEX,
ZRANGEBYSCORE,
ZRANK,
ZREVRANGE,
Expand Down Expand Up @@ -1578,6 +1579,10 @@ private RespCommand FastParseArrayCommand(ref int count, ref ReadOnlySpan<byte>
{
return RespCommand.ZRANGESTORE;
}
else if (*(ulong*)(ptr + 2) == MemoryMarshal.Read<ulong>("1\r\nZRANG"u8) && *(ulong*)(ptr + 10) == MemoryMarshal.Read<ulong>("EBYLEX\r\n"u8))
{
return RespCommand.ZRANGEBYLEX;
}
else if (*(ulong*)(ptr + 2) == MemoryMarshal.Read<ulong>("1\r\nZINTE"u8) && *(ulong*)(ptr + 10) == MemoryMarshal.Read<ulong>("RSTORE\r\n"u8))
{
return RespCommand.ZINTERSTORE;
Expand Down
1 change: 1 addition & 0 deletions libs/server/Resp/RespServerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ private bool ProcessArrayCommands<TGarnetApi>(RespCommand cmd, ref TGarnetApi st
RespCommand.ZINCRBY => SortedSetIncrement(ref storageApi),
RespCommand.ZRANK => SortedSetRank(cmd, ref storageApi),
RespCommand.ZRANGE => SortedSetRange(cmd, ref storageApi),
RespCommand.ZRANGEBYLEX => SortedSetRange(cmd, ref storageApi),
RespCommand.ZRANGESTORE => SortedSetRangeStore(ref storageApi),
RespCommand.ZRANGEBYSCORE => SortedSetRange(cmd, ref storageApi),
RespCommand.ZREVRANK => SortedSetRank(cmd, ref storageApi),
Expand Down
1 change: 1 addition & 0 deletions libs/server/Transaction/TxnKeyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ internal int GetKeys(RespCommand command, int inputCount, out ReadOnlySpan<byte>
RespCommand.ZINCRBY => SortedSetObjectKeys(SortedSetOperation.ZINCRBY, inputCount),
RespCommand.ZRANK => SortedSetObjectKeys(SortedSetOperation.ZRANK, inputCount),
RespCommand.ZRANGE => SortedSetObjectKeys(SortedSetOperation.ZRANGE, inputCount),
RespCommand.ZRANGEBYLEX => SortedSetObjectKeys(SortedSetOperation.ZRANGEBYLEX, inputCount),
RespCommand.ZRANGEBYSCORE => SortedSetObjectKeys(SortedSetOperation.ZRANGEBYSCORE, inputCount),
RespCommand.ZREVRANK => SortedSetObjectKeys(SortedSetOperation.ZREVRANK, inputCount),
RespCommand.ZREMRANGEBYLEX => SortedSetObjectKeys(SortedSetOperation.ZREMRANGEBYLEX, inputCount),
Expand Down
1 change: 1 addition & 0 deletions playground/CommandInfoUpdater/SupportedCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ public class SupportedCommand
new("ZPOPMIN", RespCommand.ZPOPMIN),
new("ZRANDMEMBER", RespCommand.ZRANDMEMBER),
new("ZRANGE", RespCommand.ZRANGE),
new("ZRANGEBYLEX", RespCommand.ZRANGEBYLEX),
new("ZRANGEBYSCORE", RespCommand.ZRANGEBYSCORE),
new("ZRANGESTORE", RespCommand.ZRANGESTORE),
new("ZRANK", RespCommand.ZRANK),
Expand Down
21 changes: 21 additions & 0 deletions test/Garnet.test/Resp/ACL/RespCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6403,6 +6403,27 @@ static async Task DoZRangeStoreAsync(GarnetClient client)
}
}

[Test]
prvyk marked this conversation as resolved.
Show resolved Hide resolved
public async Task ZRangeByLexACLsAsync()
{
await CheckCommandsAsync(
"ZRANGEBYLEX",
[DoZRangeByLexAsync, DoZRangeByLexLimitAsync]
);

static async Task DoZRangeByLexAsync(GarnetClient client)
{
string[] val = await client.ExecuteForStringArrayResultAsync("ZRANGEBYLEX", ["key", "10", "20"]);
ClassicAssert.AreEqual(0, val.Length);
}

static async Task DoZRangeByLexLimitAsync(GarnetClient client)
{
string[] val = await client.ExecuteForStringArrayResultAsync("ZRANGEBYLEX", ["key", "10", "20", "LIMIT", "2", "3"]);
ClassicAssert.AreEqual(0, val.Length);
}
}

[Test]
public async Task ZRangeByScoreACLsAsync()
{
Expand Down
29 changes: 29 additions & 0 deletions test/Garnet.test/RespSortedSetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ public void AddWithOptions()
var added = db.SortedSetAdd(key, entries);
ClassicAssert.AreEqual(entries.Length, added);

var lex = db.SortedSetRangeByValue(key, default, "c");
CollectionAssert.AreEqual(new RedisValue[] { "a", "b", "c" }, lex);

// XX - Only update elements that already exist. Don't add new elements.
var testEntries = new[]
{
Expand All @@ -200,6 +203,8 @@ public void AddWithOptions()

added = db.SortedSetAdd(key, testEntries, SortedSetWhen.Exists);
ClassicAssert.AreEqual(0, added);
lex = db.SortedSetRangeByValue(key, default, "c");
CollectionAssert.AreEqual(new RedisValue[] { "a", "c", "b" }, lex);
var scores = db.SortedSetScores(key, [new RedisValue("a"), new RedisValue("b")]);
CollectionAssert.AreEqual(new double[] { 3, 4 }, scores);
var count = db.SortedSetLength(key);
Expand All @@ -216,6 +221,8 @@ public void AddWithOptions()

added = db.SortedSetAdd(key, testEntries, SortedSetWhen.NotExists);
ClassicAssert.AreEqual(2, added);
lex = db.SortedSetRangeByValue(key, default, "c");
CollectionAssert.AreEqual(new RedisValue[] { "a", "c", "b" }, lex);
scores = db.SortedSetScores(key, [new RedisValue("a"), new RedisValue("b"), new RedisValue("k"), new RedisValue("l")]);
CollectionAssert.AreEqual(new double[] { 3, 4, 11, 12 }, scores);
count = db.SortedSetLength(key);
Expand All @@ -231,6 +238,8 @@ public void AddWithOptions()

added = db.SortedSetAdd(key, testEntries, SortedSetWhen.LessThan);
ClassicAssert.AreEqual(1, added);
lex = db.SortedSetRangeByValue(key, default, "c");
CollectionAssert.AreEqual(new RedisValue[] { "a", "b", "c" }, lex);
scores = db.SortedSetScores(key, [new RedisValue("a"), new RedisValue("b"), new RedisValue("m")]);
CollectionAssert.AreEqual(new double[] { 3, 3, 13 }, scores);
count = db.SortedSetLength(key);
Expand All @@ -246,6 +255,8 @@ public void AddWithOptions()

added = db.SortedSetAdd(key, testEntries, SortedSetWhen.GreaterThan);
ClassicAssert.AreEqual(1, added);
lex = db.SortedSetRangeByValue(key, default, "c");
CollectionAssert.AreEqual(new RedisValue[] { "b", "c", "a" }, lex);
scores = db.SortedSetScores(key, [new RedisValue("a"), new RedisValue("b"), new RedisValue("n")]);
CollectionAssert.AreEqual(new double[] { 4, 3, 14 }, scores);
count = db.SortedSetLength(key);
Expand Down Expand Up @@ -2044,6 +2055,12 @@ public void CanDoZRangeByLex()
expectedResponse = "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n";
actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length);
ClassicAssert.AreEqual(expectedResponse, actualValue);

// ZRANGEBYLEX Synonym
response = lightClientRequest.SendCommand("ZRANGEBYLEX board - [c", 4);
//expectedResponse = "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n";
actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length);
ClassicAssert.AreEqual(expectedResponse, actualValue);
}

[Test]
Expand Down Expand Up @@ -2082,6 +2099,12 @@ public void CanDoZRangeByLexReverse()
expectedResponse = "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n";
actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length);
ClassicAssert.AreEqual(expectedResponse, actualValue);

// ZREVRANGEBYLEX Synonym
response = lightClientRequest.SendCommand("ZREVRANGEBYLEX board [c - REV", 4);
//expectedResponse = "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n";
actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length);
ClassicAssert.AreEqual(expectedResponse, actualValue);
}

[Test]
Expand All @@ -2098,6 +2121,12 @@ public void CanDoZRangeByLexWithLimit()
expectedResponse = "*3\r\n$7\r\nNewYork\r\n$5\r\nParis\r\n$5\r\nSeoul\r\n";
actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length);
ClassicAssert.AreEqual(expectedResponse, actualValue);

// ZRANGEBYLEX Synonym
response = lightClientRequest.SendCommand("ZRANGEBYLEX mycity - + LIMIT 2 3", 4);
//expectedResponse = "*3\r\n$7\r\nNewYork\r\n$5\r\nParis\r\n$5\r\nSeoul\r\n";
actualValue = Encoding.ASCII.GetString(response).Substring(0, expectedResponse.Length);
ClassicAssert.AreEqual(expectedResponse, actualValue);
}


Expand Down