Commit 97d16d37 authored by Marc Gravell's avatar Marc Gravell

ZSCAN|HSCAN|SSCAN resumable iterators

parent e815fcb0
...@@ -148,7 +148,7 @@ public void HashLength() ...@@ -148,7 +148,7 @@ public void HashLength()
[Test] [Test]
public void HashScan() public void HashScan()
{ {
wrapper.HashScan("key", "pattern", 123, CommandFlags.HighPriority); wrapper.HashScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, CommandFlags.HighPriority));
} }
...@@ -603,7 +603,7 @@ public void SetRemove_2() ...@@ -603,7 +603,7 @@ public void SetRemove_2()
[Test] [Test]
public void SetScan() public void SetScan()
{ {
wrapper.SetScan("key", "pattern", 123, CommandFlags.HighPriority); wrapper.SetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority));
} }
...@@ -773,7 +773,7 @@ public void SortedSetRemoveRangeByValue() ...@@ -773,7 +773,7 @@ public void SortedSetRemoveRangeByValue()
[Test] [Test]
public void SortedSetScan() public void SortedSetScan()
{ {
wrapper.SortedSetScan("key", "pattern", 123, CommandFlags.HighPriority); wrapper.SortedSetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority));
} }
......
...@@ -82,6 +82,12 @@ internal static Exception NotSupported(bool includeDetail, RedisCommand command) ...@@ -82,6 +82,12 @@ internal static Exception NotSupported(bool includeDetail, RedisCommand command)
if (includeDetail) AddDetail(ex, null, null, s); if (includeDetail) AddDetail(ex, null, null, s);
return ex; return ex;
} }
internal static Exception NoCursor(RedisCommand command)
{
string s = GetLabel(false, command, null);
var ex = new RedisCommandException("Command cannot be used with a cursor: " + s);
return ex;
}
internal static Exception Timeout(bool includeDetail, string errorMessage, Message message, ServerEndPoint server) internal static Exception Timeout(bool includeDetail, string errorMessage, Message message, ServerEndPoint server)
{ {
......
...@@ -138,7 +138,14 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -138,7 +138,14 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// </summary> /// </summary>
/// <returns>yields all elements of the hash.</returns> /// <returns>yields all elements of the hash.</returns>
/// <remarks>http://redis.io/commands/hscan</remarks> /// <remarks>http://redis.io/commands/hscan</remarks>
IEnumerable<HashEntry> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None); IEnumerable<HashEntry> HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags);
/// <summary>
/// The HSCAN command is used to incrementally iterate over a hash
/// </summary>
/// <returns>yields all elements of the hash.</returns>
/// <remarks>http://redis.io/commands/hscan</remarks>
IEnumerable<HashEntry> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorEnumerable.DefaultPageSize, long cursor = RedisBase.CursorEnumerable.Origin, CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Sets the specified fields to their respective values in the hash stored at key. This command overwrites any existing fields in the hash. If key does not exist, a new key holding a hash is created. /// Sets the specified fields to their respective values in the hash stored at key. This command overwrites any existing fields in the hash. If key does not exist, a new key holding a hash is created.
...@@ -572,7 +579,14 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -572,7 +579,14 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// </summary> /// </summary>
/// <returns>yields all elements of the set.</returns> /// <returns>yields all elements of the set.</returns>
/// <remarks>http://redis.io/commands/sscan</remarks> /// <remarks>http://redis.io/commands/sscan</remarks>
IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None); IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags);
/// <summary>
/// The SSCAN command is used to incrementally iterate over set
/// </summary>
/// <returns>yields all elements of the set.</returns>
/// <remarks>http://redis.io/commands/sscan</remarks>
IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorEnumerable.DefaultPageSize, long cursor = RedisBase.CursorEnumerable.Origin, CommandFlags flags = CommandFlags.None);
/// <summary> /// <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 /// 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
...@@ -758,7 +772,14 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -758,7 +772,14 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// </summary> /// </summary>
/// <returns>yields all elements of the sorted set.</returns> /// <returns>yields all elements of the sorted set.</returns>
/// <remarks>http://redis.io/commands/zscan</remarks> /// <remarks>http://redis.io/commands/zscan</remarks>
IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None); IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags);
/// <summary>
/// The ZSCAN command is used to incrementally iterate over a sorted set
/// </summary>
/// <returns>yields all elements of the sorted set.</returns>
/// <remarks>http://redis.io/commands/zscan</remarks>
IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorEnumerable.DefaultPageSize, long cursor = RedisBase.CursorEnumerable.Origin, CommandFlags flags = CommandFlags.None);
/// <summary> /// <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. /// 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> /// </summary>
......
...@@ -237,7 +237,7 @@ public partial interface IServer : IRedis ...@@ -237,7 +237,7 @@ public partial interface IServer : IRedis
/// <remarks>Warning: consider KEYS as a command that should only be used in production environments with extreme care.</remarks> /// <remarks>Warning: consider KEYS as a command that should only be used in production environments with extreme care.</remarks>
/// <remarks>http://redis.io/commands/keys</remarks> /// <remarks>http://redis.io/commands/keys</remarks>
/// <remarks>http://redis.io/commands/scan</remarks> /// <remarks>http://redis.io/commands/scan</remarks>
IEnumerable<RedisKey> Keys(int database = 0, RedisValue pattern = default(RedisValue), int pageSize = 10, long cursor = 0, CommandFlags flags = CommandFlags.None); IEnumerable<RedisKey> Keys(int database = 0, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorEnumerable.DefaultPageSize, long cursor = RedisBase.CursorEnumerable.Origin, CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Return the time of the last DB save executed with success. A client may check if a BGSAVE command succeeded reading the LASTSAVE value, then issuing a BGSAVE command and checking at regular intervals every N seconds if LASTSAVE changed. /// Return the time of the last DB save executed with success. A client may check if a BGSAVE command succeeded reading the LASTSAVE value, then issuing a BGSAVE command and checking at regular intervals every N seconds if LASTSAVE changed.
......
...@@ -608,17 +608,30 @@ public TimeSpan Ping(CommandFlags flags = CommandFlags.None) ...@@ -608,17 +608,30 @@ public TimeSpan Ping(CommandFlags flags = CommandFlags.None)
return this.Inner.Ping(flags); return this.Inner.Ping(flags);
} }
public IEnumerable<HashEntry> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = 10, CommandFlags flags = CommandFlags.None)
IEnumerable<HashEntry> IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
{
return HashScan(key, pattern, pageSize, RedisBase.CursorEnumerable.Origin, flags);
}
public IEnumerable<HashEntry> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorEnumerable.DefaultPageSize, long cursor = RedisBase.CursorEnumerable.Origin, CommandFlags flags = CommandFlags.None)
{ {
return this.Inner.HashScan(this.ToInner(key), pattern, pageSize, flags); return this.Inner.HashScan(this.ToInner(key), pattern, pageSize, flags);
} }
public IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = 10, CommandFlags flags = CommandFlags.None) IEnumerable<RedisValue> IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
{ {
return this.Inner.SetScan(this.ToInner(key), pattern, pageSize, flags); return SetScan(key, pattern, pageSize, RedisBase.CursorEnumerable.Origin, flags);
}
public IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorEnumerable.DefaultPageSize, long cursor = RedisBase.CursorEnumerable.Origin, CommandFlags flags = CommandFlags.None)
{
return this.Inner.SetScan(this.ToInner(key), pattern, pageSize, cursor, flags);
} }
public IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = 10, CommandFlags flags = CommandFlags.None) IEnumerable<SortedSetEntry> IDatabase.SortedSetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
{
return SortedSetScan(key, pattern, pageSize, RedisBase.CursorEnumerable.Origin, flags);
}
public IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisBase.CursorEnumerable.DefaultPageSize, long cursor = RedisBase.CursorEnumerable.Origin, CommandFlags flags = CommandFlags.None)
{ {
return this.Inner.SortedSetScan(this.ToInner(key), pattern, pageSize, flags); return this.Inner.SortedSetScan(this.ToInner(key), pattern, pageSize, flags);
} }
......
...@@ -141,9 +141,9 @@ private ResultProcessor.TimingProcessor.TimerMessage GetTimerMessage(CommandFlag ...@@ -141,9 +141,9 @@ private ResultProcessor.TimingProcessor.TimerMessage GetTimerMessage(CommandFlag
} }
internal abstract class CursorEnumerableBase internal abstract class CursorEnumerable
{ {
internal const int DefaultPageSize = 10; internal const int Origin = 0, DefaultPageSize = 10;
internal static bool IsNil(RedisValue pattern) internal static bool IsNil(RedisValue pattern)
{ {
if (pattern.IsNullOrEmpty) return true; if (pattern.IsNullOrEmpty) return true;
...@@ -152,7 +152,7 @@ internal static bool IsNil(RedisValue pattern) ...@@ -152,7 +152,7 @@ internal static bool IsNil(RedisValue pattern)
return rawValue.Length == 1 && rawValue[0] == '*'; return rawValue.Length == 1 && rawValue[0] == '*';
} }
} }
internal abstract class CursorEnumerableBase<T> : CursorEnumerableBase, IEnumerable<T>, IScanning internal abstract class CursorEnumerable<T> : CursorEnumerable, IEnumerable<T>, IScanning
{ {
private readonly RedisBase redis; private readonly RedisBase redis;
private readonly ServerEndPoint server; private readonly ServerEndPoint server;
...@@ -161,7 +161,7 @@ internal abstract class CursorEnumerableBase<T> : CursorEnumerableBase, IEnumera ...@@ -161,7 +161,7 @@ internal abstract class CursorEnumerableBase<T> : CursorEnumerableBase, IEnumera
protected readonly int pageSize; protected readonly int pageSize;
protected readonly long initialCursor; protected readonly long initialCursor;
protected CursorEnumerableBase(RedisBase redis, ServerEndPoint server, int db, int pageSize, long cursor, CommandFlags flags) protected CursorEnumerable(RedisBase redis, ServerEndPoint server, int db, int pageSize, long cursor, CommandFlags flags)
{ {
this.redis = redis; this.redis = redis;
this.server = server; this.server = server;
...@@ -217,8 +217,8 @@ protected ScanResult Wait(Task<ScanResult> pending) ...@@ -217,8 +217,8 @@ protected ScanResult Wait(Task<ScanResult> pending)
class CursorEnumerator : IEnumerator<T>, IScanning class CursorEnumerator : IEnumerator<T>, IScanning
{ {
private CursorEnumerableBase<T> parent; private CursorEnumerable<T> parent;
public CursorEnumerator(CursorEnumerableBase<T> parent) public CursorEnumerator(CursorEnumerable<T> parent)
{ {
if (parent == null) throw new ArgumentNullException("parent"); if (parent == null) throw new ArgumentNullException("parent");
this.parent = parent; this.parent = parent;
......
...@@ -271,13 +271,13 @@ public Task<string> InfoRawAsync(RedisValue section = default(RedisValue), Comma ...@@ -271,13 +271,13 @@ public Task<string> InfoRawAsync(RedisValue section = default(RedisValue), Comma
IEnumerable<RedisKey> IServer.Keys(int database, RedisValue pattern, int pageSize, CommandFlags flags) IEnumerable<RedisKey> IServer.Keys(int database, RedisValue pattern, int pageSize, CommandFlags flags)
{ {
return Keys(database, pattern, pageSize, 0, flags); return Keys(database, pattern, pageSize, CursorEnumerable.Origin, flags);
} }
public IEnumerable<RedisKey> Keys(int database = 0, RedisValue pattern = default(RedisValue), int pageSize = CursorEnumerableBase.DefaultPageSize, long cursor = 0, CommandFlags flags = CommandFlags.None) public IEnumerable<RedisKey> Keys(int database = 0, RedisValue pattern = default(RedisValue), int pageSize = CursorEnumerable.DefaultPageSize, long cursor = CursorEnumerable.Origin, CommandFlags flags = CommandFlags.None)
{ {
if (pageSize <= 0) throw new ArgumentOutOfRangeException("pageSize"); if (pageSize <= 0) throw new ArgumentOutOfRangeException("pageSize");
if (CursorEnumerableBase.IsNil(pattern)) pattern = RedisLiterals.Wildcard; if (CursorEnumerable.IsNil(pattern)) pattern = RedisLiterals.Wildcard;
if (multiplexer.CommandMap.IsAvailable(RedisCommand.SCAN)) if (multiplexer.CommandMap.IsAvailable(RedisCommand.SCAN))
{ {
...@@ -286,7 +286,7 @@ public IEnumerable<RedisKey> Keys(int database = 0, RedisValue pattern = default ...@@ -286,7 +286,7 @@ public IEnumerable<RedisKey> Keys(int database = 0, RedisValue pattern = default
if (features.Scan) return new KeysScanEnumerable(this, database, pattern, pageSize, cursor, flags); if (features.Scan) return new KeysScanEnumerable(this, database, pattern, pageSize, cursor, flags);
} }
if (cursor != 0) throw new InvalidOperationException("A cursor cannot be used with KEYS"); if (cursor != 0) throw ExceptionFactory.NoCursor(RedisCommand.KEYS);
Message msg = Message.Create(database, flags, RedisCommand.KEYS, pattern); Message msg = Message.Create(database, flags, RedisCommand.KEYS, pattern);
return ExecuteSync(msg, ResultProcessor.RedisKeyArray); return ExecuteSync(msg, ResultProcessor.RedisKeyArray);
} }
...@@ -640,7 +640,7 @@ public static RedisValue Hash(string value) ...@@ -640,7 +640,7 @@ public static RedisValue Hash(string value)
} }
} }
sealed class KeysScanEnumerable : CursorEnumerableBase<RedisKey> sealed class KeysScanEnumerable : CursorEnumerable<RedisKey>
{ {
private readonly RedisValue pattern; private readonly RedisValue pattern;
......
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