From 27156c845b5e2e5d05a821b21e2360c0b1956ddb Mon Sep 17 00:00:00 2001 From: prvyk Date: Tue, 15 Jul 2025 18:13:16 +0300 Subject: [PATCH 01/13] Add MODULE HELP command --- libs/resources/RespCommandsDocs.json | 7 ++++++ libs/resources/RespCommandsInfo.json | 7 ++++++ libs/server/Resp/AdminCommands.cs | 22 +++++++++++++++++++ libs/server/Resp/Parser/RespCommand.cs | 7 ++++++ libs/server/Resp/RespServerSessionOutput.cs | 9 ++++++++ .../CommandInfoUpdater/SupportedCommand.cs | 1 + test/Garnet.test/Resp/ACL/RespCommandTests.cs | 17 +++++++++++++- test/Garnet.test/RespCommandTests.cs | 1 + website/docs/commands/api-compatibility.md | 2 +- 9 files changed, 71 insertions(+), 2 deletions(-) diff --git a/libs/resources/RespCommandsDocs.json b/libs/resources/RespCommandsDocs.json index e4d86c25bb6..b70d35bf1a4 100644 --- a/libs/resources/RespCommandsDocs.json +++ b/libs/resources/RespCommandsDocs.json @@ -5499,6 +5499,13 @@ "Group": "Server", "Complexity": "Depends on subcommand.", "SubCommands": [ + { + "Command": "MODULE_HELP", + "Name": "MODULE|HELP", + "Summary": "Show helpful text about the different subcommands", + "Group": "Server", + "Complexity": "O(1)" + }, { "Command": "MODULE_LOADCS", "Name": "MODULE|LOADCS", diff --git a/libs/resources/RespCommandsInfo.json b/libs/resources/RespCommandsInfo.json index f381ba1548f..8a60f5dbd6d 100644 --- a/libs/resources/RespCommandsInfo.json +++ b/libs/resources/RespCommandsInfo.json @@ -3285,6 +3285,13 @@ "Arity": -3, "Flags": "Admin, NoAsyncLoading, NoScript", "AclCategories": "Admin, Dangerous, Slow" + }, + { + "Command": "MODULE_HELP", + "Name": "MODULE|HELP", + "Arity": 2, + "Flags": "Loading, Stale", + "AclCategories": "Slow" } ] }, diff --git a/libs/server/Resp/AdminCommands.cs b/libs/server/Resp/AdminCommands.cs index 634203a3f9a..0d8e3ec4382 100644 --- a/libs/server/Resp/AdminCommands.cs +++ b/libs/server/Resp/AdminCommands.cs @@ -72,6 +72,7 @@ RespCommand.MIGRATE or RespCommand.ACL_SAVE => NetworkAclSave(), RespCommand.DEBUG => NetworkDebug(), RespCommand.REGISTERCS => NetworkRegisterCs(storeWrapper.customCommandManager), + RespCommand.MODULE_HELP => NetworkModuleHelp(), RespCommand.MODULE_LOADCS => NetworkModuleLoad(storeWrapper.customCommandManager), RespCommand.PURGEBP => NetworkPurgeBP(), _ => cmdFound = false @@ -523,6 +524,27 @@ private bool NetworkRegisterCs(CustomCommandManager customCommandManager) return true; } + private bool NetworkModuleHelp() + { + if (parseState.Count != 0) + { + return AbortWithWrongNumberOfArguments($"{RespCommand.MODULE}|{Encoding.ASCII.GetString(CmdStrings.HELP)}"); + } + + if (!CanRunModule()) + { + return AbortWithErrorMessage(CmdStrings.GenericErrCommandDisallowedWithOption, RespCommand.MODULE, "enable-module-command"); + } + + WriteHelp( + "MODULE [ [value] [opt] ...]. Subcommands are:", + "LOADCS ", + "\tLoad a module library from ", + "HELP", + "\tPrint this help."); + return true; + } + private bool NetworkModuleLoad(CustomCommandManager customCommandManager) { if (parseState.Count < 1) // At least module path is required diff --git a/libs/server/Resp/Parser/RespCommand.cs b/libs/server/Resp/Parser/RespCommand.cs index 9d4224d56c8..81dc082cef3 100644 --- a/libs/server/Resp/Parser/RespCommand.cs +++ b/libs/server/Resp/Parser/RespCommand.cs @@ -258,6 +258,7 @@ public enum RespCommand : ushort MONITOR, MODULE, + MODULE_HELP, MODULE_LOADCS, REGISTERCS, @@ -411,6 +412,7 @@ public static class RespCommandExtensions RespCommand.SWAPDB, RespCommand.ECHO, RespCommand.MONITOR, + RespCommand.MODULE_HELP, RespCommand.MODULE_LOADCS, RespCommand.REGISTERCS, RespCommand.INFO, @@ -2501,6 +2503,11 @@ private RespCommand SlowParseCommand(ReadOnlySpan command, ref int count, return RespCommand.MODULE_LOADCS; } + if (subCommand.SequenceEqual(CmdStrings.HELP)) + { + return RespCommand.MODULE_HELP; + } + string errMsg = string.Format(CmdStrings.GenericErrUnknownSubCommandNoHelp, Encoding.UTF8.GetString(subCommand), nameof(RespCommand.MODULE)); diff --git a/libs/server/Resp/RespServerSessionOutput.cs b/libs/server/Resp/RespServerSessionOutput.cs index 40ac592401a..21334e1f6cd 100644 --- a/libs/server/Resp/RespServerSessionOutput.cs +++ b/libs/server/Resp/RespServerSessionOutput.cs @@ -123,6 +123,15 @@ private void WriteError(ReadOnlySpan errorString) SendAndReset(); } + private void WriteHelp(params string[] Help) + { + WriteArrayLength(Help.Length); + foreach (var help in Help) + { + WriteSimpleString(help); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteInt32(int value) { diff --git a/playground/CommandInfoUpdater/SupportedCommand.cs b/playground/CommandInfoUpdater/SupportedCommand.cs index 89d9a8da159..810cba7511b 100644 --- a/playground/CommandInfoUpdater/SupportedCommand.cs +++ b/playground/CommandInfoUpdater/SupportedCommand.cs @@ -222,6 +222,7 @@ public class SupportedCommand new("PURGEBP", RespCommand.PURGEBP), new("MODULE", RespCommand.MODULE, [ + new("MODULE|HELP", RespCommand.MODULE_HELP), new("MODULE|LOADCS", RespCommand.MODULE_LOADCS), ]), new("MONITOR", RespCommand.MONITOR), diff --git a/test/Garnet.test/Resp/ACL/RespCommandTests.cs b/test/Garnet.test/Resp/ACL/RespCommandTests.cs index 3b02ac68853..fb0739117e9 100644 --- a/test/Garnet.test/Resp/ACL/RespCommandTests.cs +++ b/test/Garnet.test/Resp/ACL/RespCommandTests.cs @@ -4815,10 +4815,25 @@ static async Task DoPurgeBPAsync(GarnetClient client) } [Test] - public async Task ModuleLoadCSACLsAsync() + public async Task ModuleHelpACLsAsync() { // MODULE isn't a proper redis command, but this is the placeholder today... so validate it for completeness + await CheckCommandsAsync( + "MODULE", + [DoModuleHelpAsync] + ); + + static async Task DoModuleHelpAsync(GarnetClient client) + { + var result = await client.ExecuteForStringArrayResultAsync("MODULE", ["HELP"]); + ClassicAssert.IsNotNull(result); + } + } + [Test] + public async Task ModuleLoadCSACLsAsync() + { + // MODULE isn't a proper redis command, but this is the placeholder today... so validate it for completeness await CheckCommandsAsync( "MODULE", [DoModuleLoadAsync] diff --git a/test/Garnet.test/RespCommandTests.cs b/test/Garnet.test/RespCommandTests.cs index f04feb86e9f..aece3684e51 100644 --- a/test/Garnet.test/RespCommandTests.cs +++ b/test/Garnet.test/RespCommandTests.cs @@ -432,6 +432,7 @@ public void AofIndependentCommandsTest() RespCommand.SWAPDB, RespCommand.ECHO, RespCommand.MONITOR, + RespCommand.MODULE_HELP, RespCommand.MODULE_LOADCS, RespCommand.REGISTERCS, RespCommand.INFO, diff --git a/website/docs/commands/api-compatibility.md b/website/docs/commands/api-compatibility.md index dfb9ab38087..2aa58616e8e 100644 --- a/website/docs/commands/api-compatibility.md +++ b/website/docs/commands/api-compatibility.md @@ -236,7 +236,7 @@ Note that this list is subject to change as we continue to expand our API comman | | PURGE | ➖ | | | | STATS | ➖ | | | | [USAGE](server.md#memory-usage) | ➕ | | -| **MODULE** | HELP | ➖ | | +| **MODULE** | HELP | ➕ | | | | LIST | ➖ | | | | LOAD | ➖ | | | | LOADEX | ➖ | | From 3dfefb91099343c3f57cb9957b12615a4891ee72 Mon Sep 17 00:00:00 2001 From: prvyk Date: Sun, 29 Jun 2025 15:14:34 +0300 Subject: [PATCH 02/13] Add ACL HELP --- libs/resources/RespCommandsDocs.json | 7 +++ libs/resources/RespCommandsInfo.json | 7 +++ libs/server/Resp/ACLCommands.cs | 43 +++++++++++++++++++ libs/server/Resp/AdminCommands.cs | 1 + libs/server/Resp/Parser/RespCommand.cs | 6 +++ .../CommandInfoUpdater/SupportedCommand.cs | 1 + test/Garnet.test/Resp/ACL/RespCommandTests.cs | 15 +++++++ test/Garnet.test/RespCommandTests.cs | 1 + website/docs/commands/acl.md | 12 ++++++ website/docs/commands/api-compatibility.md | 2 +- 10 files changed, 94 insertions(+), 1 deletion(-) diff --git a/libs/resources/RespCommandsDocs.json b/libs/resources/RespCommandsDocs.json index b70d35bf1a4..1a3f21007a6 100644 --- a/libs/resources/RespCommandsDocs.json +++ b/libs/resources/RespCommandsDocs.json @@ -68,6 +68,13 @@ } ] }, + { + "Command": "ACL_HELP", + "Name": "ACL|HELP", + "Summary": "Show helpful text about the different subcommands", + "Group": "Server", + "Complexity": "O(1)" + }, { "Command": "ACL_LIST", "Name": "ACL|LIST", diff --git a/libs/resources/RespCommandsInfo.json b/libs/resources/RespCommandsInfo.json index 8a60f5dbd6d..4d167bc505c 100644 --- a/libs/resources/RespCommandsInfo.json +++ b/libs/resources/RespCommandsInfo.json @@ -86,6 +86,13 @@ "Arity": -2, "Flags": "Loading, NoScript, Stale", "AclCategories": "Slow" + }, + { + "Command": "ACL_HELP", + "Name": "ACL|HELP", + "Arity": 2, + "Flags": "Loading, Stale", + "AclCategories": "Slow" } ] }, diff --git a/libs/server/Resp/ACLCommands.cs b/libs/server/Resp/ACLCommands.cs index 216363d5b98..a9ee0ceba2c 100644 --- a/libs/server/Resp/ACLCommands.cs +++ b/libs/server/Resp/ACLCommands.cs @@ -476,5 +476,48 @@ private bool NetworkAclGetUser() return true; } + + /// + /// Processes ACL HELP subcommand. + /// + /// true if parsing succeeded correctly, false if not all tokens could be consumed and further processing is necessary. + private bool NetworkAclHelp() + { + // No arguments allowed + if (parseState.Count != 0) + { + return AbortWithWrongNumberOfArguments("acl|help"); + } + + WriteHelp( + "ACL [ [value] [opt] ...]. Subcommands are:", + "CAT []", + "\tList all commands that belong to , or all command categories", + "\twhen no category is specified.", + "DELUSER [ ...]", + "\tDelete a list of users.", + "GETUSER ", + "\tGet the user's details.", + "GENPASS []", + "\tGenerate a secure 256-bit user password. The optional `bits` argument can", + "\tbe used to specify a different size.", + "LIST", + "\tShow users details in config file format.", + "LOAD", + "\tReload users from the ACL file.", + "SAVE", + "\tSave the current config to the ACL file.", + "SETUSER [ ...]", + "\tCreate or modify a user with the specified attributes.", + "USERS", + "\tList all the registered usernames.", + "WHOAMI", + "\tReturn the current connection username.", + "HELP", + "\tPrint this help." + ); + + return true; + } } } \ No newline at end of file diff --git a/libs/server/Resp/AdminCommands.cs b/libs/server/Resp/AdminCommands.cs index 0d8e3ec4382..f6763dbee6f 100644 --- a/libs/server/Resp/AdminCommands.cs +++ b/libs/server/Resp/AdminCommands.cs @@ -65,6 +65,7 @@ RespCommand.MIGRATE or RespCommand.ACL_DELUSER => NetworkAclDelUser(), RespCommand.ACL_GENPASS => NetworkAclGenPass(), RespCommand.ACL_GETUSER => NetworkAclGetUser(), + RespCommand.ACL_HELP => NetworkAclHelp(), RespCommand.ACL_LIST => NetworkAclList(), RespCommand.ACL_LOAD => NetworkAclLoad(), RespCommand.ACL_SETUSER => NetworkAclSetUser(), diff --git a/libs/server/Resp/Parser/RespCommand.cs b/libs/server/Resp/Parser/RespCommand.cs index 81dc082cef3..c47604a6735 100644 --- a/libs/server/Resp/Parser/RespCommand.cs +++ b/libs/server/Resp/Parser/RespCommand.cs @@ -302,6 +302,7 @@ public enum RespCommand : ushort ACL_DELUSER, ACL_GENPASS, ACL_GETUSER, + ACL_HELP, ACL_LIST, ACL_LOAD, ACL_SAVE, @@ -423,6 +424,7 @@ public static class RespCommandExtensions RespCommand.ACL_DELUSER, RespCommand.ACL_GENPASS, RespCommand.ACL_GETUSER, + RespCommand.ACL_HELP, RespCommand.ACL_LIST, RespCommand.ACL_LOAD, RespCommand.ACL_SAVE, @@ -2441,6 +2443,10 @@ private RespCommand SlowParseCommand(ReadOnlySpan command, ref int count, { return RespCommand.ACL_GETUSER; } + else if (subCommand.SequenceEqual(CmdStrings.HELP)) + { + return RespCommand.ACL_HELP; + } else if (subCommand.SequenceEqual(CmdStrings.LIST)) { return RespCommand.ACL_LIST; diff --git a/playground/CommandInfoUpdater/SupportedCommand.cs b/playground/CommandInfoUpdater/SupportedCommand.cs index 810cba7511b..18845648950 100644 --- a/playground/CommandInfoUpdater/SupportedCommand.cs +++ b/playground/CommandInfoUpdater/SupportedCommand.cs @@ -18,6 +18,7 @@ public class SupportedCommand new("ACL|DELUSER", RespCommand.ACL_DELUSER), new("ACL|GENPASS", RespCommand.ACL_GENPASS), new("ACL|GETUSER", RespCommand.ACL_GETUSER), + new("ACL|HELP", RespCommand.ACL_HELP), new("ACL|LIST", RespCommand.ACL_LIST), new("ACL|LOAD", RespCommand.ACL_LOAD), new("ACL|SAVE", RespCommand.ACL_SAVE), diff --git a/test/Garnet.test/Resp/ACL/RespCommandTests.cs b/test/Garnet.test/Resp/ACL/RespCommandTests.cs index fb0739117e9..72aa5637005 100644 --- a/test/Garnet.test/Resp/ACL/RespCommandTests.cs +++ b/test/Garnet.test/Resp/ACL/RespCommandTests.cs @@ -212,6 +212,21 @@ static async Task DoAclGetUserAsync(GarnetClient client) } } + [Test] + public async Task AclHelpACLsAsync() + { + await CheckCommandsAsync( + "ACL HELP", + [DoAclListAsync] + ); + + static async Task DoAclListAsync(GarnetClient client) + { + string[] val = await client.ExecuteForStringArrayResultAsync("ACL", ["HELP"]); + ClassicAssert.IsNotNull(val); + } + } + [Test] public async Task AclListACLsAsync() { diff --git a/test/Garnet.test/RespCommandTests.cs b/test/Garnet.test/RespCommandTests.cs index aece3684e51..a327a372bb4 100644 --- a/test/Garnet.test/RespCommandTests.cs +++ b/test/Garnet.test/RespCommandTests.cs @@ -443,6 +443,7 @@ public void AofIndependentCommandsTest() RespCommand.ACL_DELUSER, RespCommand.ACL_GENPASS, RespCommand.ACL_GETUSER, + RespCommand.ACL_HELP, RespCommand.ACL_LIST, RespCommand.ACL_LOAD, RespCommand.ACL_SAVE, diff --git a/website/docs/commands/acl.md b/website/docs/commands/acl.md index 29348968f3c..3826a3950bd 100644 --- a/website/docs/commands/acl.md +++ b/website/docs/commands/acl.md @@ -38,6 +38,8 @@ Delete all the specified ACL users and terminate all the connections that are au Integer reply: the number of users that were deleted. This number will not always match the number of arguments since certain users may not exist. +--- + ### ACL GETUSER #### Syntax @@ -78,6 +80,16 @@ If bits was given, the output string length is the number of specified bits (rou --- +### ACL HELP + +```bash + ACL HELP +``` + +The ACL HELP command returns a helpful text describing the different subcommands. + +--- + ### ACL LIST #### Syntax diff --git a/website/docs/commands/api-compatibility.md b/website/docs/commands/api-compatibility.md index 2aa58616e8e..b02530e4474 100644 --- a/website/docs/commands/api-compatibility.md +++ b/website/docs/commands/api-compatibility.md @@ -46,7 +46,7 @@ Note that this list is subject to change as we continue to expand our API comman | | [GETUSER](acl.md#acl-getuser) | ➕ | | | | [LIST](acl.md#acl-list) | ➕ | | | | [LOAD](acl.md#acl-load) | ➕ | | -| | HELP | ➖ | | +| | HELP | ➕ | | | | LOG | ➖ | | | | [SAVE](acl.md#acl-save) | ➕ | | | | [SETUSER](acl.md#acl-setuser) | ➕ | | From 7ecd0c824378cc8bfd53e161f534ca24be6b3f0e Mon Sep 17 00:00:00 2001 From: prvyk Date: Sun, 29 Jun 2025 17:02:58 +0300 Subject: [PATCH 03/13] Convert other commands to WriteHelp() --- libs/common/RespMemoryWriter.cs | 15 +++++++++++++-- .../server/Metrics/Latency/RespLatencyCommands.cs | 11 ++--------- libs/server/Metrics/Latency/RespLatencyHelp.cs | 10 ++++------ .../server/Metrics/Slowlog/RespSlowlogCommands.cs | 12 ++---------- libs/server/Metrics/Slowlog/RespSlowlogHelp.cs | 10 ++++------ libs/server/Resp/AdminCommands.cs | 11 ++--------- website/docs/commands/api-compatibility.md | 2 +- 7 files changed, 28 insertions(+), 43 deletions(-) diff --git a/libs/common/RespMemoryWriter.cs b/libs/common/RespMemoryWriter.cs index 8d6ee383374..8a6de25e0f6 100644 --- a/libs/common/RespMemoryWriter.cs +++ b/libs/common/RespMemoryWriter.cs @@ -57,7 +57,7 @@ public void WriteAsciiDirect(ReadOnlySpan span) } /// - /// Writes an array str to memory. + /// Writes an array item to memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteArrayItem(long item) @@ -192,7 +192,6 @@ public void WriteEmptyMap() /// Write simple error to memory. /// /// An ASCII encoded error string. The string mustn't contain a CR (\r) or LF (\n) bytes. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteError(scoped ReadOnlySpan errorString) { while (!RespWriteUtils.TryWriteError(errorString, ref curr, end)) @@ -227,6 +226,18 @@ public void TryWriteFalse() } } + /// + /// Write Help output + /// + public void WriteHelp(params string[] Help) + { + WriteArrayLength(Help.Length); + foreach (var help in Help) + { + WriteSimpleString(help); + } + } + /// /// Write integer to memory. /// diff --git a/libs/server/Metrics/Latency/RespLatencyCommands.cs b/libs/server/Metrics/Latency/RespLatencyCommands.cs index d786fd8e083..b6f389d8241 100644 --- a/libs/server/Metrics/Latency/RespLatencyCommands.cs +++ b/libs/server/Metrics/Latency/RespLatencyCommands.cs @@ -20,15 +20,8 @@ private bool NetworkLatencyHelp() return AbortWithErrorMessage($"ERR Unknown subcommand or wrong number of arguments for LATENCY HELP."); } - List latencyCommands = RespLatencyHelp.GetLatencyCommands(); - while (!RespWriteUtils.TryWriteArrayLength(latencyCommands.Count, ref dcurr, dend)) - SendAndReset(); - - foreach (string command in latencyCommands) - { - while (!RespWriteUtils.TryWriteSimpleString(command, ref dcurr, dend)) - SendAndReset(); - } + var latencyCommands = RespLatencyHelp.GetLatencyCommands(); + WriteHelp(latencyCommands); return true; } diff --git a/libs/server/Metrics/Latency/RespLatencyHelp.cs b/libs/server/Metrics/Latency/RespLatencyHelp.cs index 1ba6a2cf392..ac575d42967 100644 --- a/libs/server/Metrics/Latency/RespLatencyHelp.cs +++ b/libs/server/Metrics/Latency/RespLatencyHelp.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System.Collections.Generic; - namespace Garnet.server { /// @@ -14,10 +12,10 @@ static class RespLatencyHelp /// Get supported latency commands and a short description /// /// - public static List GetLatencyCommands() + public static string[] GetLatencyCommands() { - return new List() - { + return + [ "LATENCY [ [value] [opt] ...]. Subcommands are:", "HISTOGRAM [EVENT [EVENT...]]", "\tReturn latency histogram of one or more classes.", @@ -27,7 +25,7 @@ public static List GetLatencyCommands() "\t(default: reset all data for all event classes).", "HELP", "\tPrints this help" - }; + ]; } } } \ No newline at end of file diff --git a/libs/server/Metrics/Slowlog/RespSlowlogCommands.cs b/libs/server/Metrics/Slowlog/RespSlowlogCommands.cs index 8dc86db2ee7..3ba5576a5e5 100644 --- a/libs/server/Metrics/Slowlog/RespSlowlogCommands.cs +++ b/libs/server/Metrics/Slowlog/RespSlowlogCommands.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using Garnet.common; @@ -23,15 +22,8 @@ private bool NetworkSlowLogHelp() return AbortWithWrongNumberOfArguments(nameof(RespCommand.SLOWLOG_HELP)); } - List slowLogCommands = RespSlowLogHelp.GetSlowLogCommands(); - while (!RespWriteUtils.TryWriteArrayLength(slowLogCommands.Count, ref dcurr, dend)) - SendAndReset(); - - foreach (string command in slowLogCommands) - { - while (!RespWriteUtils.TryWriteSimpleString(command, ref dcurr, dend)) - SendAndReset(); - } + var slowLogCommands = RespSlowLogHelp.GetSlowLogCommands(); + WriteHelp(slowLogCommands); return true; } diff --git a/libs/server/Metrics/Slowlog/RespSlowlogHelp.cs b/libs/server/Metrics/Slowlog/RespSlowlogHelp.cs index 12b6372f7d6..452b60de522 100644 --- a/libs/server/Metrics/Slowlog/RespSlowlogHelp.cs +++ b/libs/server/Metrics/Slowlog/RespSlowlogHelp.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System.Collections.Generic; - namespace Garnet.server { /// @@ -14,10 +12,10 @@ static class RespSlowLogHelp /// Get supported latency commands and a short description /// /// - public static List GetSlowLogCommands() + public static string[] GetSlowLogCommands() { - return new List() - { + return + [ "SLOWLOG [ [value] [opt] ...]. Subcommands are:", "GET []", "\tReturn top entries from the slowlog (default: 10, -1 mean all).", @@ -30,7 +28,7 @@ public static List GetSlowLogCommands() "\tReset the slowlog.", "HELP", "\tPrints this help" - }; + ]; } } } \ No newline at end of file diff --git a/libs/server/Resp/AdminCommands.cs b/libs/server/Resp/AdminCommands.cs index f6763dbee6f..cb2f4c2178b 100644 --- a/libs/server/Resp/AdminCommands.cs +++ b/libs/server/Resp/AdminCommands.cs @@ -776,8 +776,7 @@ private bool NetworkDebug() if (command.EqualsUpperCaseSpanIgnoringCase(CmdStrings.HELP)) { - var help = new string[] - { + WriteHelp( "DEBUG [ [value] [opt] ...]. Subcommands are:", "ERROR ", "\tReturn a Redis protocol error with as message. Useful for clients", @@ -788,13 +787,7 @@ private bool NetworkDebug() "\tCrash the server simulating a panic.", "HELP", "\tPrints this help" - }; - - WriteArrayLength(help.Length); - foreach (var line in help) - { - WriteSimpleString(line); - } + ); return true; } diff --git a/website/docs/commands/api-compatibility.md b/website/docs/commands/api-compatibility.md index b02530e4474..e7e0e34e377 100644 --- a/website/docs/commands/api-compatibility.md +++ b/website/docs/commands/api-compatibility.md @@ -203,7 +203,7 @@ Note that this list is subject to change as we continue to expand our API comman | | MOVE | ➖ | | | **LATENCY** | DOCTOR | ➖ | | | | GRAPH | ➖ | | -| | HELP | ➖ | | +| | HELP | ➕ | | | | [HISTOGRAM](server.md#latency-histogram) | ➕ | | | | HISTORY | ➖ | | | | LATEST | ➖ | | From ec978363ed6a8ed2b21ce9845bbfbd55ab2bdb5e Mon Sep 17 00:00:00 2001 From: prvyk Date: Sat, 9 Aug 2025 19:03:31 +0300 Subject: [PATCH 04/13] Add COMMAND HELP --- libs/resources/RespCommandsDocs.json | 7 ++++ libs/resources/RespCommandsInfo.json | 7 ++++ libs/server/Resp/BasicCommands.cs | 36 +++++++++++++++++++ libs/server/Resp/Parser/RespCommand.cs | 7 ++++ libs/server/Resp/RespServerSession.cs | 1 + .../CommandInfoUpdater/SupportedCommand.cs | 1 + test/Garnet.test/Resp/ACL/RespCommandTests.cs | 15 ++++++++ test/Garnet.test/RespCommandTests.cs | 1 + website/docs/commands/api-compatibility.md | 2 +- 9 files changed, 76 insertions(+), 1 deletion(-) diff --git a/libs/resources/RespCommandsDocs.json b/libs/resources/RespCommandsDocs.json index 1a3f21007a6..0dd52d53a32 100644 --- a/libs/resources/RespCommandsDocs.json +++ b/libs/resources/RespCommandsDocs.json @@ -1729,6 +1729,13 @@ } ] }, + { + "Command": "COMMAND_HELP", + "Name": "COMMAND|HELP", + "Summary": "Show helpful text about the different subcommands", + "Group": "Server", + "Complexity": "O(1)" + }, { "Command": "COMMAND_INFO", "Name": "COMMAND|INFO", diff --git a/libs/resources/RespCommandsInfo.json b/libs/resources/RespCommandsInfo.json index 4d167bc505c..e9583261ce3 100644 --- a/libs/resources/RespCommandsInfo.json +++ b/libs/resources/RespCommandsInfo.json @@ -1025,6 +1025,13 @@ "Flags": "Loading, Stale", "AclCategories": "Connection, Slow" }, + { + "Command": "COMMAND_HELP", + "Name": "COMMAND|HELP", + "Arity": 2, + "Flags": "Loading, Stale", + "AclCategories": "Slow" + }, { "Command": "COMMAND_INFO", "Name": "COMMAND|INFO", diff --git a/libs/server/Resp/BasicCommands.cs b/libs/server/Resp/BasicCommands.cs index a81fec8caeb..0d502d9eaad 100644 --- a/libs/server/Resp/BasicCommands.cs +++ b/libs/server/Resp/BasicCommands.cs @@ -1252,6 +1252,42 @@ private bool NetworkCOMMAND_GETKEYSANDFLAGS() return true; } + /// + /// Processes COMMAND HELP subcommand. + /// + private bool NetworkCOMMAND_HELP() + { + // No arguments allowed + if (parseState.Count != 0) + { + return AbortWithWrongNumberOfArguments("command|help"); + } + + WriteHelp( + "COMMAND [ [value] [opt] ...]. Subcommands are:", + "(no subcommand)", + "\tReturn details about all commands.", + "COUNT", + "\tReturn the total number of commands in this server.", + "INFO [ ...]", + "\tReturn details about multiple commands.", + "\tIf no command names are given, documentation details for all", + "\tcommands are returned.", + "DOCS [ ...]", + "\tReturn documentation details about multiple commands.", + "\tIf no command names are given, documentation details for all", + "\tcommands are returned.", + "GETKEYS ", + "\tReturn the keys from a full command.", + "GETKEYSANDFLAGS ", + "\tReturn the keys and the access flags from a full command.", + "HELP", + "\tPrint this help." + ); + + return true; + } + private bool NetworkECHO() { if (parseState.Count != 1) diff --git a/libs/server/Resp/Parser/RespCommand.cs b/libs/server/Resp/Parser/RespCommand.cs index c47604a6735..c768fd79b90 100644 --- a/libs/server/Resp/Parser/RespCommand.cs +++ b/libs/server/Resp/Parser/RespCommand.cs @@ -316,6 +316,7 @@ public enum RespCommand : ushort COMMAND_INFO, COMMAND_GETKEYS, COMMAND_GETKEYSANDFLAGS, + COMMAND_HELP, MEMORY, // MEMORY_USAGE is a read-only command, so moved up @@ -447,6 +448,7 @@ public static class RespCommandExtensions RespCommand.COMMAND_INFO, RespCommand.COMMAND_GETKEYS, RespCommand.COMMAND_GETKEYSANDFLAGS, + RespCommand.COMMAND_HELP, RespCommand.MEMORY_USAGE, // Config RespCommand.CONFIG_GET, @@ -2032,6 +2034,11 @@ private RespCommand SlowParseCommand(ReadOnlySpan command, ref int count, return RespCommand.COMMAND_GETKEYSANDFLAGS; } + if (subCommand.SequenceEqual(CmdStrings.HELP)) + { + return RespCommand.COMMAND_HELP; + } + string errMsg = string.Format(CmdStrings.GenericErrUnknownSubCommandNoHelp, Encoding.UTF8.GetString(subCommand), nameof(RespCommand.COMMAND)); diff --git a/libs/server/Resp/RespServerSession.cs b/libs/server/Resp/RespServerSession.cs index aeb3e966c36..12e7404bdf6 100644 --- a/libs/server/Resp/RespServerSession.cs +++ b/libs/server/Resp/RespServerSession.cs @@ -975,6 +975,7 @@ private bool ProcessOtherCommands(RespCommand command, ref TGarnetAp RespCommand.COMMAND_INFO => NetworkCOMMAND_INFO(), RespCommand.COMMAND_GETKEYS => NetworkCOMMAND_GETKEYS(), RespCommand.COMMAND_GETKEYSANDFLAGS => NetworkCOMMAND_GETKEYSANDFLAGS(), + RespCommand.COMMAND_HELP => NetworkCOMMAND_HELP(), RespCommand.ECHO => NetworkECHO(), RespCommand.HELLO => NetworkHELLO(), RespCommand.TIME => NetworkTIME(), diff --git a/playground/CommandInfoUpdater/SupportedCommand.cs b/playground/CommandInfoUpdater/SupportedCommand.cs index 18845648950..de378e194e2 100644 --- a/playground/CommandInfoUpdater/SupportedCommand.cs +++ b/playground/CommandInfoUpdater/SupportedCommand.cs @@ -111,6 +111,7 @@ public class SupportedCommand new("COMMAND|DOCS", RespCommand.COMMAND_DOCS), new("COMMAND|GETKEYS", RespCommand.COMMAND_GETKEYS), new("COMMAND|GETKEYSANDFLAGS", RespCommand.COMMAND_GETKEYSANDFLAGS), + new("COMMAND|HELP", RespCommand.COMMAND_HELP), ]), new("COMMITAOF", RespCommand.COMMITAOF), new("CONFIG", RespCommand.CONFIG, diff --git a/test/Garnet.test/Resp/ACL/RespCommandTests.cs b/test/Garnet.test/Resp/ACL/RespCommandTests.cs index 72aa5637005..1361ab1bd6b 100644 --- a/test/Garnet.test/Resp/ACL/RespCommandTests.cs +++ b/test/Garnet.test/Resp/ACL/RespCommandTests.cs @@ -2628,6 +2628,21 @@ static async Task DoConfigGetOneAsync(GarnetClient client) } } + [Test] + public async Task CommandHelpACLsAsync() + { + await CheckCommandsAsync( + "COMMAND", + [DoCommandHelpAsync] + ); + + static async Task DoCommandHelpAsync(GarnetClient client) + { + var result = await client.ExecuteForStringArrayResultAsync("COMMAND", ["HELP"]); + ClassicAssert.IsNotNull(result); + } + } + [Test] public async Task ConfigRewriteACLsAsync() { diff --git a/test/Garnet.test/RespCommandTests.cs b/test/Garnet.test/RespCommandTests.cs index a327a372bb4..c8908136ce8 100644 --- a/test/Garnet.test/RespCommandTests.cs +++ b/test/Garnet.test/RespCommandTests.cs @@ -466,6 +466,7 @@ public void AofIndependentCommandsTest() RespCommand.COMMAND_INFO, RespCommand.COMMAND_GETKEYS, RespCommand.COMMAND_GETKEYSANDFLAGS, + RespCommand.COMMAND_HELP, RespCommand.MEMORY_USAGE, // Config RespCommand.CONFIG_GET, diff --git a/website/docs/commands/api-compatibility.md b/website/docs/commands/api-compatibility.md index e7e0e34e377..b99e261d467 100644 --- a/website/docs/commands/api-compatibility.md +++ b/website/docs/commands/api-compatibility.md @@ -113,7 +113,7 @@ Note that this list is subject to change as we continue to expand our API comman | | [DOCS](server.md#command-docs) | ➕ | | | | [GETKEYS](server.md#command-getkeys) | ➕ | | | | [GETKEYSANDFLAGS](server.md#command-getkeysandflags) | ➕ | | -| | HELP | ➖ | | +| | HELP | ➕ | | | | [INFO](server.md#command-info) | ➕ | | | | LIST | ➖ | | | **CONNECTION** | [AUTH](generic-commands.md#auth) | ➕ | | From 8abe72d9f73be8cf5b089c0f372d965702de0af6 Mon Sep 17 00:00:00 2001 From: prvyk Date: Mon, 11 Aug 2025 02:39:05 +0300 Subject: [PATCH 05/13] Add CLIENT HELP --- libs/resources/RespCommandsDocs.json | 7 +++ libs/resources/RespCommandsInfo.json | 7 +++ libs/server/Resp/ClientCommands.cs | 55 +++++++++++++++++++ libs/server/Resp/Parser/RespCommand.cs | 6 ++ libs/server/Resp/RespServerSession.cs | 1 + .../CommandInfoUpdater/SupportedCommand.cs | 1 + test/Garnet.test/Resp/ACL/RespCommandTests.cs | 15 +++++ test/Garnet.test/RespCommandTests.cs | 1 + website/docs/commands/api-compatibility.md | 2 +- 9 files changed, 94 insertions(+), 1 deletion(-) diff --git a/libs/resources/RespCommandsDocs.json b/libs/resources/RespCommandsDocs.json index 0dd52d53a32..d90932f208a 100644 --- a/libs/resources/RespCommandsDocs.json +++ b/libs/resources/RespCommandsDocs.json @@ -951,6 +951,13 @@ "Group": "Connection", "Complexity": "O(1)" }, + { + "Command": "CLIENT_HELP", + "Name": "CLIENT|HELP", + "Summary": "Show helpful text about the different subcommands", + "Group": "Server", + "Complexity": "O(1)" + }, { "Command": "CLIENT_ID", "Name": "CLIENT|ID", diff --git a/libs/resources/RespCommandsInfo.json b/libs/resources/RespCommandsInfo.json index e9583261ce3..07fa03f7c13 100644 --- a/libs/resources/RespCommandsInfo.json +++ b/libs/resources/RespCommandsInfo.json @@ -531,6 +531,13 @@ "Flags": "Loading, NoScript, Stale", "AclCategories": "Connection, Slow" }, + { + "Command": "CLIENT_HELP", + "Name": "CLIENT|HELP", + "Arity": 2, + "Flags": "Loading, Stale", + "AclCategories": "Slow" + }, { "Command": "CLIENT_ID", "Name": "CLIENT|ID", diff --git a/libs/server/Resp/ClientCommands.cs b/libs/server/Resp/ClientCommands.cs index 2264677a146..1826e17e2f9 100644 --- a/libs/server/Resp/ClientCommands.cs +++ b/libs/server/Resp/ClientCommands.cs @@ -199,6 +199,61 @@ private bool NetworkCLIENTINFO() return true; } + /// + /// CLIENT HELP + /// + /// + private bool NetworkCLIENTHELP() + { + if (parseState.Count != 0) + { + return AbortWithWrongNumberOfArguments("client|help"); + } + + WriteHelp( + "CLIENT [ [value] [opt] ...]. Subcommands are:", + "GETNAME", + "\tReturn the name of the current connection.", + "ID", + "\tReturn the ID of the current connection.", + "INFO", + "\tReturn information about the current client connection.", + "KILL ", + "\tKill connection made from .", + "KILL