Unverified Commit faf931d3 authored by Guy Korland's avatar Guy Korland Committed by GitHub

Add support for TOUCH command (#1291)

* Add support for TOUCH command

* add async touch

* add tests

* fix tests build

* Add Delay between calls

* temp change

* Update Keys.cs

* fix tests

* add server features support for Touch

* add 3.2.1 version

* fix test
parent aec5fc36
...@@ -161,6 +161,7 @@ internal enum RedisCommand ...@@ -161,6 +161,7 @@ internal enum RedisCommand
SYNC, SYNC,
TIME, TIME,
TOUCH,
TTL, TTL,
TYPE, TYPE,
......
...@@ -1978,5 +1978,23 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -1978,5 +1978,23 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <returns>The length of the string after it was modified by the command.</returns> /// <returns>The length of the string after it was modified by the command.</returns>
/// <remarks>https://redis.io/commands/setrange</remarks> /// <remarks>https://redis.io/commands/setrange</remarks>
RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None); RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Alters the last access time of a key.
/// </summary>
/// <param name="key">The key to touch.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>True if the key was touched.</returns>
/// <remarks>https://redis.io/commands/touch</remarks>
bool KeyTouch(RedisKey key, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Alters the last access time of a keys. A key is ignored if it does not exist.
/// </summary>
/// <param name="keys">The keys to touch.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>The number of keys that were touched.</returns>
/// <remarks>https://redis.io/commands/touch</remarks>
long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None);
} }
} }
...@@ -1889,5 +1889,24 @@ public interface IDatabaseAsync : IRedisAsync ...@@ -1889,5 +1889,24 @@ public interface IDatabaseAsync : IRedisAsync
/// <returns>The length of the string after it was modified by the command.</returns> /// <returns>The length of the string after it was modified by the command.</returns>
/// <remarks>https://redis.io/commands/setrange</remarks> /// <remarks>https://redis.io/commands/setrange</remarks>
Task<RedisValue> StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None); Task<RedisValue> StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Touch the specified key.
/// </summary>
/// <param name="key">The key to touch.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>True if the key was touched.</returns>
/// <remarks>https://redis.io/commands/touch</remarks>
Task<bool> KeyTouchAsync(RedisKey key, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Youch the specified keys. A key is ignored if it does not exist.
/// </summary>
/// <param name="keys">The keys to touch.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>The number of keys that were touched.</returns>
/// <remarks>https://redis.io/commands/touch</remarks>
Task<long> KeyTouchAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None);
} }
} }
...@@ -880,5 +880,16 @@ public IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue patter ...@@ -880,5 +880,16 @@ public IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key, RedisValue patter
{ {
return Inner.SortedSetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); return Inner.SortedSetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
} }
public bool KeyTouch(RedisKey key, CommandFlags flags = CommandFlags.None)
{
return Inner.KeyTouch(ToInner(key), flags);
}
public long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{
return Inner.KeyTouch(ToInner(keys), flags);
}
} }
} }
...@@ -831,6 +831,17 @@ public Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None) ...@@ -831,6 +831,17 @@ public Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None)
return Inner.PingAsync(flags); return Inner.PingAsync(flags);
} }
public Task<long> KeyTouchAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{
return Inner.KeyTouchAsync(ToInner(keys), flags);
}
public Task<bool> KeyTouchAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{
return Inner.KeyTouchAsync(ToInner(key), flags);
}
public bool TryWait(Task task) public bool TryWait(Task task)
{ {
return Inner.TryWait(task); return Inner.TryWait(task);
......
...@@ -404,6 +404,7 @@ public static bool IsMasterOnly(RedisCommand command) ...@@ -404,6 +404,7 @@ public static bool IsMasterOnly(RedisCommand command)
case RedisCommand.SREM: case RedisCommand.SREM:
case RedisCommand.SUNIONSTORE: case RedisCommand.SUNIONSTORE:
case RedisCommand.SWAPDB: case RedisCommand.SWAPDB:
case RedisCommand.TOUCH:
case RedisCommand.UNLINK: case RedisCommand.UNLINK:
case RedisCommand.ZADD: case RedisCommand.ZADD:
case RedisCommand.ZINTERSTORE: case RedisCommand.ZINTERSTORE:
......
...@@ -2458,6 +2458,40 @@ public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, Co ...@@ -2458,6 +2458,40 @@ public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, Co
return ExecuteSync(msg, ResultProcessor.RedisValue); return ExecuteSync(msg, ResultProcessor.RedisValue);
} }
public bool KeyTouch(RedisKey key, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.TOUCH, key);
return ExecuteSync(msg, ResultProcessor.DemandZeroOrOne);
}
public long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{
if (keys == null) throw new ArgumentNullException(nameof(keys));
if (keys.Length > 0)
{
var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.TOUCH, keys);
return ExecuteSync(msg, ResultProcessor.Int64);
}
return 0;
}
public Task<bool> KeyTouchAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.TOUCH, key);
return ExecuteAsync(msg, ResultProcessor.DemandZeroOrOne);
}
public Task<long> KeyTouchAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None)
{
if (keys == null) throw new ArgumentNullException(nameof(keys));
if (keys.Length > 0)
{
var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.TOUCH, keys);
return ExecuteAsync(msg, ResultProcessor.Int64);
}
return CompletedTask<long>.Default(0);
}
public Task<RedisValue> StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) public Task<RedisValue> StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value); var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value);
......
...@@ -31,6 +31,7 @@ namespace StackExchange.Redis ...@@ -31,6 +31,7 @@ namespace StackExchange.Redis
v2_9_5 = new Version(2, 9, 5), v2_9_5 = new Version(2, 9, 5),
v3_0_0 = new Version(3, 0, 0), v3_0_0 = new Version(3, 0, 0),
v3_2_0 = new Version(3, 2, 0), v3_2_0 = new Version(3, 2, 0),
v3_2_1 = new Version(3, 2, 1),
v4_0_0 = new Version(4, 0, 0), v4_0_0 = new Version(4, 0, 0),
v4_9_1 = new Version(4, 9, 1); // 5.0 RC1 is version 4.9.1 v4_9_1 = new Version(4, 9, 1); // 5.0 RC1 is version 4.9.1
...@@ -205,6 +206,11 @@ public RedisFeatures(Version version) ...@@ -205,6 +206,11 @@ public RedisFeatures(Version version)
/// </summary> /// </summary>
public Version Version => version ?? v2_0_0; public Version Version => version ?? v2_0_0;
/// <summary>
/// Are the Touch command available?
/// </summary>
public bool KeyTouch => Version >= v3_2_1;
/// <summary> /// <summary>
/// Create a string representation of the available features /// Create a string representation of the available features
/// </summary> /// </summary>
......
...@@ -199,6 +199,27 @@ public async Task IdleTime() ...@@ -199,6 +199,27 @@ public async Task IdleTime()
} }
} }
[Fact]
public async Task TouchIdleTime()
{
using (var muxer = Create())
{
Skip.IfMissingFeature(muxer, nameof(RedisFeatures.KeyTouch), r => r.KeyTouch);
RedisKey key = Me();
var db = muxer.GetDatabase();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.StringSet(key, "new value", flags: CommandFlags.FireAndForget);
await Task.Delay(2000).ForAwait();
var idleTime = db.KeyIdleTime(key);
Assert.True(idleTime > TimeSpan.Zero);
Assert.True(db.KeyTouch(key));
var idleTime1 = db.KeyIdleTime(key);
Assert.True(idleTime1 < idleTime);
}
}
[Fact] [Fact]
public async Task IdleTimeAsync() public async Task IdleTimeAsync()
{ {
...@@ -221,5 +242,26 @@ public async Task IdleTimeAsync() ...@@ -221,5 +242,26 @@ public async Task IdleTimeAsync()
Assert.Null(idleTime3); Assert.Null(idleTime3);
} }
} }
[Fact]
public async Task TouchIdleTimeAsync()
{
using (var muxer = Create())
{
Skip.IfMissingFeature(muxer, nameof(RedisFeatures.KeyTouch), r => r.KeyTouch);
RedisKey key = Me();
var db = muxer.GetDatabase();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.StringSet(key, "new value", flags: CommandFlags.FireAndForget);
await Task.Delay(2000).ForAwait();
var idleTime = await db.KeyIdleTimeAsync(key).ForAwait();
Assert.True(idleTime > TimeSpan.Zero);
Assert.True(await db.KeyTouchAsync(key).ForAwait());
var idleTime1 = await db.KeyIdleTimeAsync(key).ForAwait();
Assert.True(idleTime1 < idleTime);
}
}
} }
} }
...@@ -1078,6 +1078,22 @@ public void StringSetRangeAsync() ...@@ -1078,6 +1078,22 @@ public void StringSetRangeAsync()
wrapper.StringSetRangeAsync("key", 123, "value", CommandFlags.None); wrapper.StringSetRangeAsync("key", 123, "value", CommandFlags.None);
mock.Verify(_ => _.StringSetRangeAsync("prefix:key", 123, "value", CommandFlags.None)); mock.Verify(_ => _.StringSetRangeAsync("prefix:key", 123, "value", CommandFlags.None));
} }
[Fact]
public void KeyTouchAsync_1()
{
wrapper.KeyTouchAsync("key", CommandFlags.None);
mock.Verify(_ => _.KeyTouchAsync("prefix:key", CommandFlags.None));
}
[Fact]
public void KeyTouchAsync_2()
{
RedisKey[] keys = new RedisKey[] { "a", "b" };
Expression<Func<RedisKey[], bool>> valid = _ => _.Length == 2 && _[0] == "prefix:a" && _[1] == "prefix:b";
wrapper.KeyTouchAsync(keys, CommandFlags.None);
mock.Verify(_ => _.KeyTouchAsync(It.Is(valid), CommandFlags.None));
}
#pragma warning restore RCS1047 // Non-asynchronous method name should not end with 'Async'. #pragma warning restore RCS1047 // Non-asynchronous method name should not end with 'Async'.
} }
} }
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