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 support for TOUCH command #1291

Merged
merged 12 commits into from
Feb 3, 2020
1 change: 1 addition & 0 deletions src/StackExchange.Redis/Enums/RedisCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ internal enum RedisCommand
SYNC,

TIME,
TOUCH,
TTL,
TYPE,

Expand Down
18 changes: 18 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1978,5 +1978,23 @@ IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key,
/// <returns>The length of the string after it was modified by the command.</returns>
/// <remarks>https://redis.io/commands/setrange</remarks>
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);
}
}
19 changes: 19 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1889,5 +1889,24 @@ Task<RedisValue[]> SortedSetRangeByValueAsync(RedisKey key,
/// <returns>The length of the string after it was modified by the command.</returns>
/// <remarks>https://redis.io/commands/setrange</remarks>
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);

}
}
11 changes: 11 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -880,5 +880,16 @@ IEnumerable<SortedSetEntry> IDatabase.SortedSetScan(RedisKey key, RedisValue pat
{
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);
}

}
}
11 changes: 11 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,17 @@ public Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None)
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)
{
return Inner.TryWait(task);
Expand Down
1 change: 1 addition & 0 deletions src/StackExchange.Redis/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ public static bool IsMasterOnly(RedisCommand command)
case RedisCommand.SREM:
case RedisCommand.SUNIONSTORE:
case RedisCommand.SWAPDB:
case RedisCommand.TOUCH:
case RedisCommand.UNLINK:
case RedisCommand.ZADD:
case RedisCommand.ZINTERSTORE:
Expand Down
34 changes: 34 additions & 0 deletions src/StackExchange.Redis/RedisDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2458,6 +2458,40 @@ public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, Co
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)
{
var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value);
Expand Down
6 changes: 6 additions & 0 deletions src/StackExchange.Redis/RedisFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public readonly struct RedisFeatures
v2_9_5 = new Version(2, 9, 5),
v3_0_0 = new Version(3, 0, 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_9_1 = new Version(4, 9, 1); // 5.0 RC1 is version 4.9.1

Expand Down Expand Up @@ -200,6 +201,11 @@ public RedisFeatures(Version version)
/// </summary>
public Version Version => version ?? v2_0_0;

/// <summary>
/// Are the Touch command available?
/// </summary>
public bool KeyTouch => Version >= v3_2_1;

/// <summary>
/// Create a string representation of the available features
/// </summary>
Expand Down
42 changes: 42 additions & 0 deletions tests/StackExchange.Redis.Tests/Keys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
public async Task IdleTimeAsync()
{
Expand All @@ -221,5 +242,26 @@ public async Task IdleTimeAsync()
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);
}
}
}
}
16 changes: 16 additions & 0 deletions tests/StackExchange.Redis.Tests/WrapperBaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,22 @@ public void StringSetRangeAsync()
wrapper.StringSetRangeAsync("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'.
}
}