Commit d475b568 authored by Marc Gravell's avatar Marc Gravell

Z*LEX* command support

parent 40a156d7
using NUnit.Framework;
namespace StackExchange.Redis.Tests
{
[TestFixture]
public class Lex : TestBase
{
protected override string GetConfiguration()
{
return "ubuntu";
}
[Test]
public void QueryRangeAndLengthByLex()
{
using(var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = Me();
db.KeyDelete(key);
db.SortedSetAdd(key,
new SortedSetEntry[]
{
new SortedSetEntry("a", 0),
new SortedSetEntry("b", 0),
new SortedSetEntry("c", 0),
new SortedSetEntry("d", 0),
new SortedSetEntry("e", 0),
new SortedSetEntry("f", 0),
new SortedSetEntry("g", 0),
});
var set = db.SortedSetRangeByValue(key, default(RedisValue), "c");
var count = db.SortedSetLengthByValue(key, default(RedisValue), "c");
Equate(set, count, "a", "b", "c");
set = db.SortedSetRangeByValue(key, default(RedisValue), "c", Exclude.Stop);
count = db.SortedSetLengthByValue(key, default(RedisValue), "c", Exclude.Stop);
Equate(set, count, "a", "b");
set = db.SortedSetRangeByValue(key, "aaa", "g", Exclude.Stop);
count = db.SortedSetLengthByValue(key, "aaa", "g", Exclude.Stop);
Equate(set, count, "b", "c", "d", "e", "f");
set = db.SortedSetRangeByValue(key, "aaa", "g", Exclude.Stop, 1, 3);
Equate(set, set.Length, "c", "d", "e");
}
}
[Test]
public void RemoveRangeByLex()
{
using (var conn = Create())
{
var db = conn.GetDatabase();
RedisKey key = Me();
db.KeyDelete(key);
db.SortedSetAdd(key,
new SortedSetEntry[]
{
new SortedSetEntry("aaaa", 0),
new SortedSetEntry("b", 0),
new SortedSetEntry("c", 0),
new SortedSetEntry("d", 0),
new SortedSetEntry("e", 0),
});
db.SortedSetAdd(key,
new SortedSetEntry[]
{
new SortedSetEntry("foo", 0),
new SortedSetEntry("zap", 0),
new SortedSetEntry("zip", 0),
new SortedSetEntry("ALPHA", 0),
new SortedSetEntry("alpha", 0),
});
var set = db.SortedSetRangeByRank(key);
Equate(set, set.Length, "ALPHA", "aaaa", "alpha", "b", "c", "d", "e", "foo", "zap", "zip");
long removed = db.SortedSetRemoveRangeByValue(key, "alpha", "omega");
Assert.AreEqual(6, removed);
set = db.SortedSetRangeByRank(key);
Equate(set, set.Length, "ALPHA", "aaaa", "zap", "zip");
}
}
private void Equate(RedisValue[] actual, long count, params string[] expected)
{
Assert.AreEqual(count, expected.Length);
Assert.AreEqual(expected.Length, actual.Length);
for(int i = 0; i < actual.Length; i++)
{
Assert.AreEqual(expected[i], (string)actual[i]);
}
}
}
}
......@@ -76,6 +76,7 @@
<Compile Include="Issues\SO22786599.cs" />
<Compile Include="Keys.cs" />
<Compile Include="KeysAndValues.cs" />
<Compile Include="Lex.cs" />
<Compile Include="Lists.cs" />
<Compile Include="Locking.cs" />
<Compile Include="MultiAdd.cs" />
......
......@@ -636,6 +636,13 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/zcard</remarks>
long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary>
/// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns the number of elements in the sorted set at key with a value between min and max.
/// </summary>
/// <returns>the number of elements in the specified score range.</returns>
/// <remarks>When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the sorted set at key with a value between min and max.</remarks>
long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score.
/// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on.
......@@ -654,6 +661,8 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/zrevrange</remarks>
SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score.
/// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on.
......@@ -678,6 +687,15 @@ public interface IDatabase : IRedis, IDatabaseAsync
Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1,
CommandFlags flags = CommandFlags.None);
/// <summary>
/// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the sorted set at key with a value between min and max.
/// </summary>
/// <remarks>http://redis.io/commands/zrangebylex</remarks>
/// <returns>list of elements in the specified score range.</returns>
RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue),
Exclude exclude = Exclude.None, long skip = 0, long take = -1,
CommandFlags flags = CommandFlags.None);
/// <summary>
/// Returns the rank of member in the sorted set stored at key, by default with the scores ordered from low to high. The rank (or index) is 0-based, which means that the member with the lowest score has rank 0.
/// </summary>
......@@ -714,6 +732,12 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/zremrangebyscore</remarks>
long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary>
/// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command removes all elements in the sorted set stored at key between the lexicographical range specified by min and max.
/// </summary>
/// <remarks>http://redis.io/commands/zremrangebylex</remarks>
/// <returns>the number of elements removed.</returns>
long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary>
/// The ZSCAN command is used to incrementally iterate over a sorted set
/// </summary>
......
......@@ -410,6 +410,7 @@ public interface IDatabaseAsync : IRedisAsync
/// <returns>the number of clients that received the message.</returns>
/// <remarks>http://redis.io/commands/publish</remarks>
Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Execute a Lua script against the server
/// </summary>
......@@ -473,6 +474,7 @@ public interface IDatabaseAsync : IRedisAsync
/// <returns>1 if the element is a member of the set. 0 if the element is not a member of the set, or if key does not exist.</returns>
/// <remarks>http://redis.io/commands/sismember</remarks>
Task<bool> SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Returns the set cardinality (number of elements) of the set stored at key.
/// </summary>
......@@ -531,7 +533,6 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>http://redis.io/commands/srem</remarks>
Task<long> SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Sorts a list, set or sorted set (numerically or alphabetically, ascending by default); By default, the elements themselves are compared, but the values can also be
/// used to perform external key-lookups using the <c>by</c> parameter. By default, the elements themselves are returned, but external key-lookups (one or many) can
......@@ -608,6 +609,14 @@ public interface IDatabaseAsync : IRedisAsync
/// <returns>the cardinality (number of elements) of the sorted set, or 0 if key does not exist.</returns>
/// <remarks>http://redis.io/commands/zcard</remarks>
Task<long> SortedSetLengthAsync(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary>
/// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns the number of elements in the sorted set at key with a value between min and max.
/// </summary>
/// <returns>the number of elements in the specified score range.</returns>
/// <remarks>When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the sorted set at key with a value between min and max.</remarks>
Task<long> SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score.
/// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on.
......@@ -617,7 +626,6 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>http://redis.io/commands/zrevrange</remarks>
Task<RedisValue[]> SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score.
/// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on.
......@@ -651,6 +659,15 @@ public interface IDatabaseAsync : IRedisAsync
Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1,
CommandFlags flags = CommandFlags.None);
/// <summary>
/// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the sorted set at key with a value between min and max.
/// </summary>
/// <remarks>http://redis.io/commands/zrangebylex</remarks>
/// <returns>list of elements in the specified score range.</returns>
Task<RedisValue[]> SortedSetRangeByValueAsync(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue),
Exclude exclude = Exclude.None, long skip = 0, long take = -1,
CommandFlags flags = CommandFlags.None);
/// <summary>
/// Returns the rank of member in the sorted set stored at key, by default with the scores ordered from low to high. The rank (or index) is 0-based, which means that the member with the lowest score has rank 0.
/// </summary>
......@@ -687,6 +704,12 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>http://redis.io/commands/zremrangebyscore</remarks>
Task<long> SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary>
/// When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command removes all elements in the sorted set stored at key between the lexicographical range specified by min and max.
/// </summary>
/// <remarks>http://redis.io/commands/zremrangebylex</remarks>
/// <returns>the number of elements removed.</returns>
Task<long> SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary>
/// 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.
/// </summary>
......
......@@ -338,6 +338,7 @@ public static bool IsMasterOnly(RedisCommand command)
case RedisCommand.ZINTERSTORE:
case RedisCommand.ZINCRBY:
case RedisCommand.ZREM:
case RedisCommand.ZREMRANGEBYLEX:
case RedisCommand.ZREMRANGEBYRANK:
case RedisCommand.ZREMRANGEBYSCORE:
case RedisCommand.ZUNIONSTORE:
......
......@@ -160,10 +160,13 @@ enum RedisCommand
ZCOUNT,
ZINCRBY,
ZINTERSTORE,
ZLEXCOUNT,
ZRANGE,
ZRANGEBYLEX,
ZRANGEBYSCORE,
ZRANK,
ZREM,
ZREMRANGEBYLEX,
ZREMRANGEBYRANK,
ZREMRANGEBYSCORE,
ZREVRANGE,
......
......@@ -1527,6 +1527,20 @@ ITransaction GetLockReleaseTransaction(RedisKey key, RedisValue value)
return tran;
}
private RedisValue GetLexRange(RedisValue value, Exclude exclude, bool isStart)
{
if(value.IsNull)
{
return isStart? RedisLiterals.MinusSymbol : RedisLiterals.PlusSumbol;
}
byte[] orig = value;
byte[] result = new byte[orig.Length + 1];
// no defaults here; must always explicitly specify [ / (
result[0] = (exclude & (isStart ? Exclude.Start : Exclude.Stop)) == 0 ? (byte)'[' : (byte)'(';
Buffer.BlockCopy(orig, 0, result, 1, orig.Length);
return result;
}
private RedisValue GetRange(double value, Exclude exclude, bool isStart)
{
if (isStart)
......@@ -1885,6 +1899,52 @@ private IEnumerable<T> TryScan<T>(RedisKey key, RedisValue pattern, int pageSize
if (ScanUtils.IsNil(pattern)) pattern = (byte[])null;
return new ScanIterator<T>(this, server, key, pattern, pageSize, flags, command, processor).Read();
}
private Message GetLexMessage(RedisCommand command, RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take, CommandFlags flags)
{
RedisValue start = GetLexRange(min, exclude, true), stop = GetLexRange(max, exclude, false);
if (skip == 0 && take == -1)
return Message.Create(Db, flags, command, key, start, stop);
return Message.Create(Db, flags, command, key, new[] { start, stop, RedisLiterals.LIMIT, skip, take });
}
public long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{
var msg = GetLexMessage(RedisCommand.ZLEXCOUNT, key, min, max, exclude, 0, -1, flags);
return ExecuteSync(msg, ResultProcessor.Int64);
}
public RedisValue[] SortedSetRangeByValue(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None)
{
var msg = GetLexMessage(RedisCommand.ZRANGEBYLEX, key, min, max, exclude, skip, take, flags);
return ExecuteSync(msg, ResultProcessor.RedisValueArray);
}
public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{
var msg = GetLexMessage(RedisCommand.ZREMRANGEBYLEX, key, min, max, exclude, 0, -1, flags);
return ExecuteSync(msg, ResultProcessor.Int64);
}
public Task<long> SortedSetLengthByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{
var msg = GetLexMessage(RedisCommand.ZLEXCOUNT, key, min, max, exclude, 0, -1, flags);
return ExecuteAsync(msg, ResultProcessor.Int64);
}
public Task<RedisValue[]> SortedSetRangeByValueAsync(RedisKey key, RedisValue min = default(RedisValue), RedisValue max = default(RedisValue), Exclude exclude = Exclude.None, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None)
{
var msg = GetLexMessage(RedisCommand.ZRANGEBYLEX, key, min, max, exclude, skip, take, flags);
return ExecuteAsync(msg, ResultProcessor.RedisValueArray);
}
public Task<long> SortedSetRemoveRangeByValueAsync(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{
var msg = GetLexMessage(RedisCommand.ZREMRANGEBYLEX, key, min, max, exclude, 0, -1, flags);
return ExecuteAsync(msg, ResultProcessor.Int64);
}
internal static class ScanUtils
{
public const int DefaultPageSize = 10;
......
......@@ -61,7 +61,10 @@ public static readonly RedisValue
no = "no",
replication = "replication",
server = "server",
Wildcard = "*";
Wildcard = "*",
PlusSumbol = "+",
MinusSymbol = "-";
public static readonly byte[] BytesOK = Encoding.UTF8.GetBytes("OK");
public static readonly byte[] BytesPONG = Encoding.UTF8.GetBytes("PONG");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment