Commit e4370fa9 authored by Marc Gravell's avatar Marc Gravell

Implemented SORT

parent b050439c
......@@ -134,6 +134,7 @@
<Compile Include="StackExchange\Redis\ServerType.cs" />
<Compile Include="StackExchange\Redis\SetOperation.cs" />
<Compile Include="StackExchange\Redis\SocketManager.cs" />
<Compile Include="StackExchange\Redis\SortType.cs" />
<Compile Include="StackExchange\Redis\StringSplits.cs" />
<Compile Include="StackExchange\Redis\TaskContinuationCheck.cs" />
<Compile Include="StackExchange\Redis\When.cs" />
......
......@@ -784,5 +784,30 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <returns>the value of key, or nil when key does not exist.</returns>
/// <remarks>http://redis.io/commands/get</remarks>
RedisValueWithExpiry StringGetWithExpiry(RedisKey key, 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
/// be performed instead by specifying the <c>get</c> parameter (note that <c>#</c> specifies the element itself, when used in <c>get</c>).
/// Referring to the <a href="http://redis.io/commands/sort">redis SORT documentation </a> for examples is recommended. When used in hashes, <c>by</c> and <c>get</c>
/// can be used to specify fields using <c>-&gt;</c> notation (again, refer to redis documentation).
/// </summary>
/// <remarks>http://redis.io/commands/sort</remarks>
/// <returns>Returns the sorted elements, or the external values if <c>get</c> is specified</returns>
[IgnoreNamePrefix]
RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, 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
/// be performed instead by specifying the <c>get</c> parameter (note that <c>#</c> specifies the element itself, when used in <c>get</c>).
/// Referring to the <a href="http://redis.io/commands/sort">redis SORT documentation </a> for examples is recommended. When used in hashes, <c>by</c> and <c>get</c>
/// can be used to specify fields using <c>-&gt;</c> notation (again, refer to redis documentation).
/// </summary>
/// <remarks>http://redis.io/commands/sort</remarks>
/// <returns>Returns the number of elements stored in the new list</returns>
[IgnoreNamePrefix]
long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None);
}
}
\ No newline at end of file
......@@ -760,6 +760,30 @@ public interface IDatabaseAsync : IRedisAsync
/// <returns>the value of key, or nil when key does not exist.</returns>
/// <remarks>http://redis.io/commands/get</remarks>
Task<RedisValueWithExpiry> StringGetWithExpiryAsync(RedisKey key, 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
/// be performed instead by specifying the <c>get</c> parameter (note that <c>#</c> specifies the element itself, when used in <c>get</c>).
/// Referring to the <a href="http://redis.io/commands/sort">redis SORT documentation </a> for examples is recommended. When used in hashes, <c>by</c> and <c>get</c>
/// can be used to specify fields using <c>-&gt;</c> notation (again, refer to redis documentation).
/// </summary>
/// <remarks>http://redis.io/commands/sort</remarks>
/// <returns>Returns the sorted elements, or the external values if <c>get</c> is specified</returns>
[IgnoreNamePrefix]
Task<RedisValue[]> SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, 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
/// be performed instead by specifying the <c>get</c> parameter (note that <c>#</c> specifies the element itself, when used in <c>get</c>).
/// Referring to the <a href="http://redis.io/commands/sort">redis SORT documentation </a> for examples is recommended. When used in hashes, <c>by</c> and <c>get</c>
/// can be used to specify fields using <c>-&gt;</c> notation (again, refer to redis documentation).
/// </summary>
/// <remarks>http://redis.io/commands/sort</remarks>
/// <returns>Returns the number of elements stored in the new list</returns>
[IgnoreNamePrefix]
Task<long> SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None);
}
......
......@@ -418,6 +418,12 @@ internal static Message Create(int db, CommandFlags flags, RedisCommand command,
}
}
internal static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisValue[] values, RedisKey key1)
{
if (values == null) throw new ArgumentNullException("values");
return new CommandKeyValuesKeyMessage(db, flags, command, key0, values, key1);
}
internal static CommandFlags GetMasterSlaveFlags(CommandFlags flags)
{
// for the purposes of the switch, we only care about two bits
......@@ -538,7 +544,7 @@ internal void WriteTo(PhysicalConnection physical)
}
private static CommandFlags SetMasterSlaveFlags(CommandFlags everything, CommandFlags masterSlave)
internal static CommandFlags SetMasterSlaveFlags(CommandFlags everything, CommandFlags masterSlave)
{
// take away the two flags we don't want, and add back the ones we care about
return (everything & ~(CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave))
......@@ -780,6 +786,33 @@ internal override void WriteImpl(PhysicalConnection physical)
}
}
sealed class CommandKeyValuesKeyMessage : CommandKeyBase
{
private readonly RedisValue[] values;
private readonly RedisKey key1;
public CommandKeyValuesKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisValue[] values, RedisKey key1) : base(db, flags, command, key0)
{
for (int i = 0; i < values.Length; i++)
{
values[i].Assert();
}
this.values = values;
this.key1 = key1.Assert();
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, values.Length + 2);
physical.Write(Key);
for (int i = 0; i < values.Length; i++) physical.Write(values[i]);
physical.Write(key1);
}
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
var slot = base.GetHashSlot(serverSelectionStrategy);
return serverSelectionStrategy.CombineSlot(slot, key1);
}
}
sealed class CommandKeyValueValueMessage : CommandKeyBase
{
private readonly RedisValue value0, value1;
......
......@@ -124,6 +124,7 @@ enum RedisCommand
SLOWLOG,
SMEMBERS,
SMOVE,
SORT,
SPOP,
SRANDMEMBER,
SREM,
......
......@@ -1592,6 +1592,106 @@ public Task<RedisValueWithExpiry> StringGetWithExpiryAsync(RedisKey key, Command
return ExecuteAsync(msg, processor, server);
}
private Message GetSortedSetAddMessage(RedisKey destination, RedisKey key, long skip, long take, Order order, SortType sortType, RedisValue by, RedisValue[] get, CommandFlags flags)
{
// most common cases; no "get", no "by", no "destination", no "skip", no "take"
if(destination.IsNull && skip == 0 && take == -1 && by.IsNull && (get == null || get.Length == 0))
{
switch(order)
{
case Order.Ascending:
switch(sortType)
{
case SortType.Numeric: return Message.Create(Db, flags, RedisCommand.SORT, key);
case SortType.Alphabetic: return Message.Create(Db, flags, RedisCommand.SORT, key, RedisLiterals.ALPHA);
}
break;
case Order.Descending:
switch (sortType)
{
case SortType.Numeric: return Message.Create(Db, flags, RedisCommand.SORT, key, RedisLiterals.DESC);
case SortType.Alphabetic: return Message.Create(Db, flags, RedisCommand.SORT, key, RedisLiterals.DESC, RedisLiterals.ALPHA);
}
break;
}
}
// and now: more complicated scenarios...
List<RedisValue> values = new List<RedisValue>();
if (!by.IsNull) {
values.Add(RedisLiterals.BY);
values.Add(by);
}
if (skip != 0 || take != -1)// these are our defaults that mean "everything"; anything else needs to be sent explicitly
{
values.Add(RedisLiterals.LIMIT);
values.Add(skip);
values.Add(take);
}
switch(order)
{
case Order.Ascending:
break; // default
case Order.Descending:
values.Add(RedisLiterals.DESC);
break;
default:
throw new ArgumentOutOfRangeException("order");
}
switch(sortType)
{
case SortType.Numeric:
break; // default
case SortType.Alphabetic:
values.Add(RedisLiterals.ALPHA);
break;
default:
throw new ArgumentOutOfRangeException("sortType");
}
if(get != null && get.Length != 0)
{
foreach(var item in get)
{
values.Add(RedisLiterals.GET);
values.Add(item);
}
}
if(destination.IsNull) return Message.Create(Db, flags, RedisCommand.SORT, key, values.ToArray());
// because we are using STORE, we need to push this to a master
if(Message.GetMasterSlaveFlags(flags) == CommandFlags.DemandSlave)
{
throw ExceptionFactory.MasterOnly(RedisCommand.SORT);
}
flags = Message.SetMasterSlaveFlags(flags, CommandFlags.DemandMaster);
values.Add(RedisLiterals.STORE);
return Message.Create(Db, flags, RedisCommand.SORT, key, values.ToArray(), destination);
}
public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None)
{
var msg = GetSortedSetAddMessage(default(RedisKey), key, skip, take, order, sortType, by, get, flags);
return ExecuteSync(msg, ResultProcessor.RedisValueArray);
}
public long SortAndStore(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None)
{
var msg = GetSortedSetAddMessage(destination, key, skip, take, order, sortType, by, get, flags);
return ExecuteSync(msg, ResultProcessor.Int64);
}
public Task<RedisValue[]> SortAsync(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None)
{
var msg = GetSortedSetAddMessage(default(RedisKey), key, skip, take, order, sortType, by, get, flags);
return ExecuteAsync(msg, ResultProcessor.RedisValueArray);
}
public Task<long> SortAndStoreAsync(RedisKey destination, RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None)
{
var msg = GetSortedSetAddMessage(destination, key, skip, take, order, sortType, by, get, flags);
return ExecuteAsync(msg, ResultProcessor.Int64);
}
private class StringGetWithExpiryProcessor : ResultProcessor<RedisValueWithExpiry>
{
public static readonly ResultProcessor<RedisValueWithExpiry> TTL = new StringGetWithExpiryProcessor(false), PTTL = new StringGetWithExpiryProcessor(true);
......
......@@ -40,6 +40,10 @@ public static readonly RedisValue
NOT = "NOT",
XOR = "XOR",
RESETSTAT = "RESETSTAT",
BY = "BY",
DESC = "DESC",
ALPHA = "ALPHA",
STORE = "STORE",
// DO NOT CHANGE CASE: these are configuration settings and MUST be as-is
databases = "databases",
......@@ -54,6 +58,7 @@ public static readonly RedisValue
public static readonly byte[] OK = Encoding.UTF8.GetBytes("OK");
public static readonly byte[] ByteWildcard = { (byte)'*' };
internal static RedisValue Get(Bitwise operation)
{
switch(operation)
......
namespace StackExchange.Redis
{
/// <summary>
/// Specifies how to compare elements for sorting
/// </summary>
public enum SortType
{
/// <summary>
/// Elements are interpreted as a double-precision floating point number and sorted numerically
/// </summary>
Numeric,
/// <summary>
/// Elements are sorted using their alphabetic form (Redis is UTF-8 aware as long as the !LC_COLLATE environment variable is set at the server)
/// </summary>
Alphabetic
}
}
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