Skip to content

Commit

Permalink
Support COMMAND [...] (#2143)
Browse files Browse the repository at this point in the history
Add support for `COMMAND` commands (part of #2055):
COMMAND COUNT - https://redis.io/commands/command-count/
COMMAND GETKEYS - https://redis.io/commands/command-getkeys/
COMMAND LIST - https://redis.io/commands/command-list/


Co-authored-by: Nick Craver <nrcraver@gmail.com>
  • Loading branch information
shacharPash and NickCraver authored May 24, 2022
1 parent 2f0c477 commit 4e8431e
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 17 deletions.
2 changes: 2 additions & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
- Adds: Support for `BIT | BYTE` to `BITCOUNT` and `BITPOS` with `.StringBitCount()`/`.StringBitCountAsync()` and `.StringBitPosition()`/`.StringBitPositionAsync()` [#2116 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2116))
- Adds: Support for pub/sub payloads that are unary arrays ([#2118 by Marc Gravell](https://github.com/StackExchange/StackExchange.Redis/pull/2118))
- Fix: Sentinel timer race during dispose ([#2133 by ewisuri](https://github.com/StackExchange/StackExchange.Redis/pull/2133))
- Adds: Support for `COMMAND COUNT`, `COMMAND GETKEYS`, and `COMMAND LIST`, with `.CommandCount()`/`.CommandCountAsync()`, `.CommandGetKeys()`/`.CommandGetKeysAsync()`, and `.CommandList()`/`.CommandListAsync()` ([#2143 by shacharPash](https://github.com/StackExchange/StackExchange.Redis/pull/2143))


## 2.5.61

Expand Down
5 changes: 5 additions & 0 deletions src/StackExchange.Redis/APITypes/LCSMatchResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ public readonly struct LCSMatchResult
{
internal static LCSMatchResult Null { get; } = new LCSMatchResult(Array.Empty<LCSMatch>(), 0);

/// <summary>
/// Whether this match result contains any matches.
/// </summary>
public bool IsEmpty => LongestMatchLength == 0 && (Matches is null || Matches.Length == 0);

/// <summary>
/// The matched positions of all the sub-matched strings.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/StackExchange.Redis/Enums/RedisCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal enum RedisCommand
CLUSTER,
CONFIG,
COPY,
COMMAND,

DBSIZE,
DEBUG,
Expand Down Expand Up @@ -361,6 +362,7 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
case RedisCommand.BITPOS:
case RedisCommand.CLIENT:
case RedisCommand.CLUSTER:
case RedisCommand.COMMAND:
case RedisCommand.CONFIG:
case RedisCommand.DBSIZE:
case RedisCommand.DEBUG:
Expand Down
35 changes: 35 additions & 0 deletions src/StackExchange.Redis/Interfaces/IServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,41 @@ public partial interface IServer : IRedis
/// <inheritdoc cref="ConfigSet(RedisValue, RedisValue, CommandFlags)"/>
Task ConfigSetAsync(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns the number of total commands available in this Redis server.
/// </summary>
/// <param name="flags">The command flags to use.</param>
/// <remarks><seealso href="https://redis.io/commands/command-count"/></remarks>
long CommandCount(CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="CommandCount(CommandFlags)"/>
Task<long> CommandCountAsync(CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns list of keys from a full Redis command.
/// </summary>
/// <param name="command">The command to get keys from.</param>
/// <param name="flags">The command flags to use.</param>
/// <remarks><seealso href="https://redis.io/commands/command-getkeys"/></remarks>
RedisKey[] CommandGetKeys(RedisValue[] command, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="CommandGetKeys(RedisValue[], CommandFlags)"/>
Task<RedisKey[]> CommandGetKeysAsync(RedisValue[] command, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Returns a list of command names available on this Redis server.
/// Only one of the filter options is usable at a time.
/// </summary>
/// <param name="moduleName">The module name to filter the command list by.</param>
/// <param name="category">The category to filter the command list by.</param>
/// <param name="pattern">The pattern to filter the command list by.</param>
/// <param name="flags">The command flags to use.</param>
/// <remarks><seealso href="https://redis.io/commands/command-list"/></remarks>
string[] CommandList(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="CommandList(RedisValue?, RedisValue?, RedisValue?, CommandFlags)"/>
Task<string[]> CommandListAsync(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Return the number of keys in the database.
/// </summary>
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 @@ -473,6 +473,7 @@ internal static bool RequiresDatabase(RedisCommand command)
case RedisCommand.BGSAVE:
case RedisCommand.CLIENT:
case RedisCommand.CLUSTER:
case RedisCommand.COMMAND:
case RedisCommand.CONFIG:
case RedisCommand.DISCARD:
case RedisCommand.ECHO:
Expand Down
7 changes: 7 additions & 0 deletions src/StackExchange.Redis/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,12 @@ StackExchange.Redis.IServer.ConfigRewrite(StackExchange.Redis.CommandFlags flags
StackExchange.Redis.IServer.ConfigRewriteAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IServer.ConfigSet(StackExchange.Redis.RedisValue setting, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void
StackExchange.Redis.IServer.ConfigSetAsync(StackExchange.Redis.RedisValue setting, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IServer.CommandCount(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IServer.CommandCountAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
StackExchange.Redis.IServer.CommandGetKeys(StackExchange.Redis.RedisValue[]! command, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisKey[]!
StackExchange.Redis.IServer.CommandGetKeysAsync(StackExchange.Redis.RedisValue[]! command, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisKey[]!>!
StackExchange.Redis.IServer.CommandList(StackExchange.Redis.RedisValue? moduleName = null, StackExchange.Redis.RedisValue? category = null, StackExchange.Redis.RedisValue? pattern = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> string![]!
StackExchange.Redis.IServer.CommandListAsync(StackExchange.Redis.RedisValue? moduleName = null, StackExchange.Redis.RedisValue? category = null, StackExchange.Redis.RedisValue? pattern = null, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<string![]!>!
StackExchange.Redis.IServer.DatabaseCount.get -> int
StackExchange.Redis.IServer.DatabaseSize(int database = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IServer.DatabaseSizeAsync(int database = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
Expand Down Expand Up @@ -1125,6 +1131,7 @@ StackExchange.Redis.LoadedLuaScript.ExecutableScript.get -> string!
StackExchange.Redis.LoadedLuaScript.Hash.get -> byte[]!
StackExchange.Redis.LoadedLuaScript.OriginalScript.get -> string!
StackExchange.Redis.LCSMatchResult
StackExchange.Redis.LCSMatchResult.IsEmpty.get -> bool
StackExchange.Redis.LCSMatchResult.LCSMatchResult() -> void
StackExchange.Redis.LCSMatchResult.LongestMatchLength.get -> long
StackExchange.Redis.LCSMatchResult.Matches.get -> StackExchange.Redis.LCSMatchResult.LCSMatch[]!
Expand Down
3 changes: 3 additions & 0 deletions src/StackExchange.Redis/RawResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ internal bool GetBoolean()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal string?[]? GetItemsAsStrings() => this.ToArray<string?>((in RawResult x) => (string?)x.AsRedisValue());

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal string[]? GetItemsAsStringsNotNullable() => this.ToArray<string>((in RawResult x) => (string)x.AsRedisValue()!);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool[]? GetItemsAsBooleans() => this.ToArray<bool>((in RawResult x) => (bool)x.AsRedisValue());

Expand Down
24 changes: 12 additions & 12 deletions src/StackExchange.Redis/RedisDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public Task<bool> GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags f
var redisValues = new RedisValue[members.Length];
for (var i = 0; i < members.Length; i++) redisValues[i] = members[i];
var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues);
return ExecuteSync(msg, ResultProcessor.StringArray, defaultValue: Array.Empty<string?>());
return ExecuteSync(msg, ResultProcessor.NullableStringArray, defaultValue: Array.Empty<string?>());
}

public Task<string?[]> GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
Expand All @@ -125,7 +125,7 @@ public Task<bool> GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags f
var redisValues = new RedisValue[members.Length];
for (var i = 0; i < members.Length; i++) redisValues[i] = members[i];
var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues);
return ExecuteAsync(msg, ResultProcessor.StringArray, defaultValue: Array.Empty<string?>());
return ExecuteAsync(msg, ResultProcessor.NullableStringArray, defaultValue: Array.Empty<string?>());
}

public string? GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
Expand Down Expand Up @@ -829,37 +829,37 @@ public Task<long> KeyExistsAsync(RedisKey[] keys, CommandFlags flags = CommandFl
return ExecuteAsync(msg, ResultProcessor.Int64);
}

public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags) =>
public bool KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) =>
KeyExpire(key, expiry, ExpireWhen.Always, flags);

public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags) =>
public bool KeyExpire(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) =>
KeyExpire(key, expiry, ExpireWhen.Always, flags);

public bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
public bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None)
{
var msg = GetExpiryMessage(key, flags, expiry, when, out ServerEndPoint? server);
return ExecuteSync(msg, ResultProcessor.Boolean, server: server);
}

public bool KeyExpire(RedisKey key, DateTime? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
public bool KeyExpire(RedisKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None)
{
var msg = GetExpiryMessage(key, flags, expiry, when, out ServerEndPoint? server);
return ExecuteSync(msg, ResultProcessor.Boolean, server: server);
}

public Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags) =>
public Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) =>
KeyExpireAsync(key, expiry, ExpireWhen.Always, flags);

public Task<bool> KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags) =>
public Task<bool> KeyExpireAsync(RedisKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None) =>
KeyExpireAsync(key, expiry, ExpireWhen.Always, flags);

public Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
public Task<bool> KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None)
{
var msg = GetExpiryMessage(key, flags, expiry, when, out ServerEndPoint? server);
return ExecuteAsync(msg, ResultProcessor.Boolean, server: server);
}

public Task<bool> KeyExpireAsync(RedisKey key, DateTime? expire, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
public Task<bool> KeyExpireAsync(RedisKey key, DateTime? expire, ExpireWhen when, CommandFlags flags = CommandFlags.None)
{
var msg = GetExpiryMessage(key, flags, expire, when, out ServerEndPoint? server);
return ExecuteAsync(msg, ResultProcessor.Boolean, server: server);
Expand Down Expand Up @@ -1470,13 +1470,13 @@ public Task<long> StringLongestCommonSubsequenceLengthAsync(RedisKey key1, Redis
public LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey key1, RedisKey key2, long minSubMatchLength = 0, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.IDX, RedisLiterals.MINMATCHLEN, minSubMatchLength, RedisLiterals.WITHMATCHLEN);
return ExecuteSync(msg, ResultProcessor.LCSMatchResult, defaultValue: LCSMatchResult.Null);
return ExecuteSync(msg, ResultProcessor.LCSMatchResult);
}

public Task<LCSMatchResult> StringLongestCommonSubsequenceWithMatchesAsync(RedisKey key1, RedisKey key2, long minSubMatchLength = 0, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.IDX, RedisLiterals.MINMATCHLEN, minSubMatchLength, RedisLiterals.WITHMATCHLEN);
return ExecuteAsync(msg, ResultProcessor.LCSMatchResult, defaultValue: LCSMatchResult.Null);
return ExecuteAsync(msg, ResultProcessor.LCSMatchResult);
}

public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
Expand Down
5 changes: 5 additions & 0 deletions src/StackExchange.Redis/RedisLiterals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ internal static class RedisLiterals
// unlike primary commands, these do not get altered by the command-map; we may as
// well compute the bytes once and share them
public static readonly RedisValue
ACLCAT = "ACLCAT",
ADDR = "ADDR",
AFTER = "AFTER",
AGGREGATE = "AGGREGATE",
Expand All @@ -64,9 +65,11 @@ public static readonly RedisValue
EX = "EX",
EXAT = "EXAT",
EXISTS = "EXISTS",
FILTERBY = "FILTERBY",
FLUSH = "FLUSH",
FREQ = "FREQ",
GET = "GET",
GETKEYS = "GETKEYS",
GETNAME = "GETNAME",
GT = "GT",
HISTORY = "HISTORY",
Expand All @@ -88,6 +91,7 @@ public static readonly RedisValue
MAXLEN = "MAXLEN",
MIN = "MIN",
MINMATCHLEN = "MINMATCHLEN",
MODULE = "MODULE",
NODES = "NODES",
NOSAVE = "NOSAVE",
NOT = "NOT",
Expand All @@ -96,6 +100,7 @@ public static readonly RedisValue
NX = "NX",
OBJECT = "OBJECT",
OR = "OR",
PATTERN = "PATTERN",
PAUSE = "PAUSE",
PERSIST = "PERSIST",
PING = "PING",
Expand Down
73 changes: 73 additions & 0 deletions src/StackExchange.Redis/RedisServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,79 @@ public Task ConfigSetAsync(RedisValue setting, RedisValue value, CommandFlags fl
return task;
}

public long CommandCount(CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.COUNT);
return ExecuteSync(msg, ResultProcessor.Int64);
}

public Task<long> CommandCountAsync(CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.COUNT);
return ExecuteAsync(msg, ResultProcessor.Int64);
}

public RedisKey[] CommandGetKeys(RedisValue[] command, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(-1, flags, RedisCommand.COMMAND, AddValueToArray(RedisLiterals.GETKEYS, command));
return ExecuteSync(msg, ResultProcessor.RedisKeyArray, defaultValue: Array.Empty<RedisKey>());
}

public Task<RedisKey[]> CommandGetKeysAsync(RedisValue[] command, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(-1, flags, RedisCommand.COMMAND, AddValueToArray(RedisLiterals.GETKEYS, command));
return ExecuteAsync(msg, ResultProcessor.RedisKeyArray, defaultValue: Array.Empty<RedisKey>());
}

public string[] CommandList(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None)
{
var msg = GetCommandListMessage(moduleName, category, pattern, flags);
return ExecuteSync(msg, ResultProcessor.StringArray, defaultValue: Array.Empty<string>());
}

public Task<string[]> CommandListAsync(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None)
{
var msg = GetCommandListMessage(moduleName, category, pattern, flags);
return ExecuteAsync(msg, ResultProcessor.StringArray, defaultValue: Array.Empty<string>());
}

private Message GetCommandListMessage(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None)
{
if (moduleName == null && category == null && pattern == null)
{
return Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.LIST);
}

else if (moduleName != null && category == null && pattern == null)
{
return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.MODULE, (RedisValue)moduleName));
}

else if (moduleName == null && category != null && pattern == null)
{
return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.ACLCAT, (RedisValue)category));
}

else if (moduleName == null && category == null && pattern != null)
{
return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.PATTERN, (RedisValue)pattern));
}

else
throw new ArgumentException("More then one filter is not allwed");
}

private RedisValue[] AddValueToArray(RedisValue val, RedisValue[] arr)
{
var result = new RedisValue[arr.Length + 1];
var i = 0;
result[i++] = val;
foreach (var item in arr) result[i++] = item;
return result;
}

private RedisValue[] MakeArray(params RedisValue[] redisValues) { return redisValues; }

public long DatabaseSize(int database = -1, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.DBSIZE);
Expand Down
Loading

0 comments on commit 4e8431e

Please sign in to comment.