diff --git a/pom.xml b/pom.xml index bf0d821595..6c9e3263dd 100644 --- a/pom.xml +++ b/pom.xml @@ -360,6 +360,8 @@ **/*IntegrationTest.java **/*IntegrationTests.java + + **/codegen/**/*.java @@ -535,6 +537,7 @@ **/MultiDb*.java **/ClientTestUtil.java **/ReflectionTestUtil.java + **/*CommandFlags*.java diff --git a/src/main/java/redis/clients/jedis/CommandArguments.java b/src/main/java/redis/clients/jedis/CommandArguments.java index da51c098e1..a793a463c9 100644 --- a/src/main/java/redis/clients/jedis/CommandArguments.java +++ b/src/main/java/redis/clients/jedis/CommandArguments.java @@ -195,6 +195,16 @@ public int size() { return args.size(); } + /** + * Get the argument at the specified index. + * @param index the index of the argument to retrieve (0-based, where 0 is the command itself) + * @return the Rawable argument at the specified index + * @throws IndexOutOfBoundsException if the index is out of range + */ + public Rawable get(int index) { + return args.get(index); + } + @Override public Iterator iterator() { return args.iterator(); diff --git a/src/main/java/redis/clients/jedis/CommandFlagsRegistry.java b/src/main/java/redis/clients/jedis/CommandFlagsRegistry.java new file mode 100644 index 0000000000..7822739767 --- /dev/null +++ b/src/main/java/redis/clients/jedis/CommandFlagsRegistry.java @@ -0,0 +1,52 @@ +package redis.clients.jedis; + +import java.util.EnumSet; + +/** + * Registry interface for command flags. Provides a mapping from Redis commands to their flags. This + * interface allows for different implementations of the flags registry. + */ +public interface CommandFlagsRegistry { + + /** + * Command flags based on command flags exposed by Redis. See + * Command flags for more + * details. + *

+ * Flags description: + *

+ */ + enum CommandFlag { + READONLY, WRITE, DENYOOM, ADMIN, PUBSUB, NOSCRIPT, SORT_FOR_SCRIPT, LOADING, STALE, + SKIP_MONITOR, SKIP_SLOWLOG, ASKING, FAST, MOVABLEKEYS, MODULE, BLOCKING, NO_AUTH, + NO_ASYNC_LOADING, NO_MULTI, NO_MANDATORY_KEYS, ALLOW_BUSY + } + + /** + * Get the flags for a given command. + * @param commandArguments the command arguments containing the command and its parameters + * @return EnumSet of CommandFlag for this command, or empty set if command has no flags + */ + EnumSet getFlags(CommandArguments commandArguments); +} diff --git a/src/main/java/redis/clients/jedis/StaticCommandFlagsRegistry.java b/src/main/java/redis/clients/jedis/StaticCommandFlagsRegistry.java new file mode 100644 index 0000000000..cb561dd92c --- /dev/null +++ b/src/main/java/redis/clients/jedis/StaticCommandFlagsRegistry.java @@ -0,0 +1,207 @@ +package redis.clients.jedis; + +import redis.clients.jedis.args.Rawable; +import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.util.JedisByteMap; +import redis.clients.jedis.util.SafeEncoder; + +import java.util.EnumSet; +import java.util.Map; + +/** + * Static implementation of CommandFlagsRegistry. + */ +public class StaticCommandFlagsRegistry implements CommandFlagsRegistry { + + // Empty flags constant for commands with no flags + public static final EnumSet EMPTY_FLAGS = EnumSet.noneOf(CommandFlag.class); + + // Singleton instance + private static final StaticCommandFlagsRegistry REGISTRY = createRegistry(); + + private final Commands commands; + + private StaticCommandFlagsRegistry(Commands commands) { + this.commands = commands; + } + + /** + * Get the singleton instance of the static command flags registry. + *

+ * DO NOT USE THIS METHOD UNLESS YOU KNOW WHAT YOU ARE DOING. + *

+ * @return StaticCommandFlagsRegistry + */ + public static StaticCommandFlagsRegistry registry() { + return REGISTRY; + } + + private static StaticCommandFlagsRegistry createRegistry() { + + Builder builder = new Builder(); + + // Delegate population to generated class + StaticCommandFlagsRegistryInitializer.initialize(builder); + + return builder.build(); + } + + /** + * Get the flags for a given command. Flags are looked up from a static registry based on the + * command arguments. This approach significantly reduces memory usage by sharing flag instances + * across all CommandObject instances. + *

+ * For commands with subcommands (e.g., FUNCTION LOAD, ACL SETUSER), this method implements a + * hierarchical lookup strategy: + *

    + *
  1. First, retrieve the parent command using CommandArguments.getCommand()
  2. + *
  3. Check if this is a parent command (has subcommands in the registry)
  4. + *
  5. If it is a parent command, attempt to get the child/subcommand by: + *
      + *
    • Extracting the second argument from the CommandArguments object
    • + *
    • Matching this second argument against the child items of the parent command
    • + *
    + *
  6. + *
  7. Return the appropriate flags based on whether a child command was found or just use the + * parent command's flags
  8. + *
+ * @param commandArguments the command arguments containing the command and its parameters + * @return EnumSet of CommandFlag for this command, or empty set if command has no flags + */ + @Override + public EnumSet getFlags(CommandArguments commandArguments) { + // Get the parent command + ProtocolCommand cmd = commandArguments.getCommand(); + byte[] raw = cmd.getRaw(); + + // Convert to uppercase using SafeEncoder utility (faster than String.toUpperCase()) + byte[] uppercaseBytes = SafeEncoder.toUpperCase(raw); + + // Look up the parent command in the registry using byte array key + // Object registryEntry = COMMAND_FLAGS_REGISTRY.get(uppercaseBytes); + CommandMeta commandMeta = commands.getCommand(uppercaseBytes); + + if (commandMeta == null) { + // Command not found in registry + return EMPTY_FLAGS; + } + + if (!commandMeta.hasSubcommands()) { + // Check if this is a simple command without subcommands + return commandMeta.getFlags(); + } else { + // Parent command with subcommands + // Try to extract the subcommand from the second argument + byte[] subCommand = getSubCommand(commandArguments); + if (subCommand != null) { + CommandMeta subCommandMeta = commandMeta.getSubcommand(subCommand); + if (subCommandMeta != null) { + return subCommandMeta.getFlags(); + } else { + // (second argument exists but not a recognized subcommand , return parent flags + return commandMeta.getFlags(); + } + } else { + // no second argument (no subcommand), return parent flags + return commandMeta.getFlags(); + } + } + } + + private byte[] getSubCommand(CommandArguments commandArguments) { + if (commandArguments.size() > 1) { + Rawable secondArg = commandArguments.get(1); + byte[] subRaw = secondArg.getRaw(); + + // Convert to uppercase using SafeEncoder utility + return SafeEncoder.toUpperCase(subRaw); + } else { + return null; + } + } + + // Internal class to hold subcommand mappings for parent commands. + static class Commands { + + final JedisByteMap commands = new JedisByteMap<>(); + + boolean isEmpty() { + return commands.isEmpty(); + } + + public Commands register(byte[] cmd, CommandMeta command) { + commands.put(cmd, command); + return this; + } + + public boolean containsKey(byte[] command) { + return commands.containsKey(command); + } + + public CommandMeta getCommand(byte[] command) { + return commands.get(command); + } + + public Map getCommands() { + return commands; + } + } + + // + static class CommandMeta { + + final EnumSet flags; + + final Commands subcommands = new Commands(); + + CommandMeta(EnumSet flags) { + this.flags = flags; + } + + void putSubCommand(byte[] subCommand, CommandMeta subCommandMeta) { + this.subcommands.register(subCommand, subCommandMeta); + } + + boolean hasSubcommands() { + return !subcommands.isEmpty(); + } + + EnumSet getFlags() { + if (flags == null) { + return EMPTY_FLAGS; + } + + return flags; + } + + CommandMeta getSubcommand(byte[] subcommand) { + return subcommands.getCommand(subcommand); + } + } + + static public class Builder { + + private final Commands commands = new Commands(); + + public Builder register(String name, EnumSet flags) { + commands.register(SafeEncoder.encode(name), new CommandMeta(flags)); + return this; + } + + public Builder register(String name, String subcommand, EnumSet flags) { + byte[] cmdName = SafeEncoder.encode(name); + + if (!commands.containsKey(cmdName)) { + commands.register(SafeEncoder.encode(name), new CommandMeta(EMPTY_FLAGS)); + } + + byte[] subCmdName = SafeEncoder.encode(subcommand); + commands.getCommand(cmdName).putSubCommand(subCmdName, new CommandMeta(flags)); + return this; + } + + public StaticCommandFlagsRegistry build() { + return new StaticCommandFlagsRegistry(commands); + } + } +} diff --git a/src/main/java/redis/clients/jedis/StaticCommandFlagsRegistryInitializer.java b/src/main/java/redis/clients/jedis/StaticCommandFlagsRegistryInitializer.java new file mode 100644 index 0000000000..78b0f3903b --- /dev/null +++ b/src/main/java/redis/clients/jedis/StaticCommandFlagsRegistryInitializer.java @@ -0,0 +1,847 @@ +package redis.clients.jedis; + +import java.util.EnumSet; +import static redis.clients.jedis.StaticCommandFlagsRegistry.EMPTY_FLAGS; +import static redis.clients.jedis.CommandFlagsRegistry.CommandFlag; + +/** + * Static implementation of CommandFlagsRegistry. This class is auto-generated by + * CommandFlagsRegistryGenerator. DO NOT EDIT MANUALLY. + *

+ * Generated from Redis Server: + *

    + *
  • Version: 8.2.3
  • + *
  • Mode: standalone
  • + *
  • Loaded Modules: timeseries, search, bf, vectorset, ReJSON
  • + *
  • Generated at: 2025-11-04 20:10:39 EET
  • + *
+ */ +final class StaticCommandFlagsRegistryInitializer { + + static void initialize(StaticCommandFlagsRegistry.Builder builder) { + builder.register("ACL", EMPTY_FLAGS); // ACL parent command with subcommands + builder.register("ACL", "CAT", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "DELUSER", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "DRYRUN", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "GENPASS", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "GETUSER", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "LIST", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "LOAD", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "LOG", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "SAVE", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "SETUSER", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "USERS", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("ACL", "WHOAMI", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("OBJECT", EMPTY_FLAGS); // OBJECT parent command with subcommands + builder.register("OBJECT", "ENCODING", EnumSet.of(CommandFlag.READONLY)); + builder.register("OBJECT", "FREQ", EnumSet.of(CommandFlag.READONLY)); + builder.register("OBJECT", "IDLETIME", EnumSet.of(CommandFlag.READONLY)); + builder.register("OBJECT", "REFCOUNT", EnumSet.of(CommandFlag.READONLY)); + builder.register("FUNCTION", EMPTY_FLAGS); // FUNCTION parent command with subcommands + builder.register("FUNCTION", "DELETE", EnumSet.of(CommandFlag.NOSCRIPT, CommandFlag.WRITE)); + builder.register("FUNCTION", "DUMP", EnumSet.of(CommandFlag.NOSCRIPT)); + builder.register("FUNCTION", "FLUSH", EnumSet.of(CommandFlag.NOSCRIPT, CommandFlag.WRITE)); + builder.register("FUNCTION", "KILL", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.NOSCRIPT)); + builder.register("FUNCTION", "LIST", EnumSet.of(CommandFlag.NOSCRIPT)); + builder.register("FUNCTION", "LOAD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.NOSCRIPT, CommandFlag.WRITE)); + builder.register("FUNCTION", "RESTORE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.NOSCRIPT, CommandFlag.WRITE)); + builder.register("FUNCTION", "STATS", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.NOSCRIPT)); + builder.register("CLIENT", EMPTY_FLAGS); // CLIENT parent command with subcommands + builder.register("CLIENT", "CACHING", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "GETNAME", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "GETREDIR", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "ID", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "INFO", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "KILL", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "LIST", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "NO-EVICT", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "NO-TOUCH", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "PAUSE", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "REPLY", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "SETINFO", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "SETNAME", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "TRACKING", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "TRACKINGINFO", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "UNBLOCK", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLIENT", "UNPAUSE", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CONFIG", EMPTY_FLAGS); // CONFIG parent command with subcommands + builder.register("CONFIG", "GET", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CONFIG", "RESETSTAT", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CONFIG", "REWRITE", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CONFIG", "SET", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("MODULE", EMPTY_FLAGS); // MODULE parent command with subcommands + builder.register("MODULE", "LIST", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT)); + builder.register("MODULE", "LOAD", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING)); + builder.register("MODULE", "LOADEX", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING)); + builder.register("MODULE", "UNLOAD", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING)); + builder.register("PUBSUB", EMPTY_FLAGS); // PUBSUB parent command with subcommands + builder.register("PUBSUB", "CHANNELS", + EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("PUBSUB", "NUMPAT", + EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("PUBSUB", "NUMSUB", + EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("PUBSUB", "SHARDCHANNELS", + EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("PUBSUB", "SHARDNUMSUB", + EnumSet.of(CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("SCRIPT", EMPTY_FLAGS); // SCRIPT parent command with subcommands + builder.register("SCRIPT", "DEBUG", EnumSet.of(CommandFlag.NOSCRIPT)); + builder.register("SCRIPT", "EXISTS", EnumSet.of(CommandFlag.NOSCRIPT)); + builder.register("SCRIPT", "FLUSH", EnumSet.of(CommandFlag.NOSCRIPT)); + builder.register("SCRIPT", "KILL", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.NOSCRIPT)); + builder.register("SCRIPT", "LOAD", EnumSet.of(CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("MEMORY", EMPTY_FLAGS); // MEMORY parent command with subcommands + builder.register("MEMORY", "DOCTOR", EMPTY_FLAGS); + builder.register("MEMORY", "MALLOC-STATS", EMPTY_FLAGS); + builder.register("MEMORY", "PURGE", EMPTY_FLAGS); + builder.register("MEMORY", "STATS", EMPTY_FLAGS); + builder.register("MEMORY", "USAGE", EnumSet.of(CommandFlag.READONLY)); + builder.register("SLOWLOG", EMPTY_FLAGS); // SLOWLOG parent command with subcommands + builder.register("SLOWLOG", "GET", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("SLOWLOG", "LEN", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("SLOWLOG", "RESET", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("COMMAND", EMPTY_FLAGS); // COMMAND parent command with subcommands + builder.register("COMMAND", "COUNT", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("COMMAND", "DOCS", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("COMMAND", "GETKEYS", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("COMMAND", "GETKEYSANDFLAGS", + EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("COMMAND", "INFO", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("COMMAND", "LIST", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("LATENCY", EMPTY_FLAGS); // LATENCY parent command with subcommands + builder.register("LATENCY", "DOCTOR", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("LATENCY", "GRAPH", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("LATENCY", "HISTOGRAM", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("LATENCY", "HISTORY", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("LATENCY", "LATEST", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("LATENCY", "RESET", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLUSTER", EMPTY_FLAGS); // CLUSTER parent command with subcommands + builder.register("CLUSTER", "ADDSLOTS", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "ADDSLOTSRANGE", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "BUMPEPOCH", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "COUNT-FAILURE-REPORTS", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.STALE)); + builder.register("CLUSTER", "COUNTKEYSINSLOT", EnumSet.of(CommandFlag.STALE)); + builder.register("CLUSTER", "DELSLOTS", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "DELSLOTSRANGE", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "FAILOVER", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "FLUSHSLOTS", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "FORGET", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "GETKEYSINSLOT", EnumSet.of(CommandFlag.STALE)); + builder.register("CLUSTER", "INFO", EnumSet.of(CommandFlag.STALE)); + builder.register("CLUSTER", "KEYSLOT", EnumSet.of(CommandFlag.STALE)); + builder.register("CLUSTER", "LINKS", EnumSet.of(CommandFlag.STALE)); + builder.register("CLUSTER", "MEET", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "MYID", EnumSet.of(CommandFlag.STALE)); + builder.register("CLUSTER", "MYSHARDID", EnumSet.of(CommandFlag.STALE)); + builder.register("CLUSTER", "NODES", EnumSet.of(CommandFlag.STALE)); + builder.register("CLUSTER", "REPLICAS", EnumSet.of(CommandFlag.ADMIN, CommandFlag.STALE)); + builder.register("CLUSTER", "REPLICATE", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "RESET", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("CLUSTER", "SAVECONFIG", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "SET-CONFIG-EPOCH", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "SETSLOT", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "SHARDS", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "SLAVES", EnumSet.of(CommandFlag.ADMIN, CommandFlag.STALE)); + builder.register("CLUSTER", "SLOT-STATS", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("CLUSTER", "SLOTS", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("XINFO", EMPTY_FLAGS); // XINFO parent command with subcommands + builder.register("XINFO", "CONSUMERS", EnumSet.of(CommandFlag.READONLY)); + builder.register("XINFO", "GROUPS", EnumSet.of(CommandFlag.READONLY)); + builder.register("XINFO", "STREAM", EnumSet.of(CommandFlag.READONLY)); + builder.register("XGROUP", EMPTY_FLAGS); // XGROUP parent command with subcommands + builder.register("XGROUP", "CREATE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("XGROUP", "CREATECONSUMER", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("XGROUP", "DELCONSUMER", EnumSet.of(CommandFlag.WRITE)); + builder.register("XGROUP", "DESTROY", EnumSet.of(CommandFlag.WRITE)); + builder.register("XGROUP", "SETID", EnumSet.of(CommandFlag.WRITE)); + // 1 command(s) with: admin + builder.register("PFSELFTEST", EnumSet.of(CommandFlag.ADMIN)); + + // 2 command(s) with: blocking + builder.register("WAIT", EnumSet.of(CommandFlag.BLOCKING)); + builder.register("WAITAOF", EnumSet.of(CommandFlag.BLOCKING)); + + // 2 command(s) with: fast + builder.register("ASKING", EnumSet.of(CommandFlag.FAST)); + builder.register("PING", EnumSet.of(CommandFlag.FAST)); + + // 41 command(s) with: readonly + builder.register("BITCOUNT", EnumSet.of(CommandFlag.READONLY)); + builder.register("BITPOS", EnumSet.of(CommandFlag.READONLY)); + builder.register("DUMP", EnumSet.of(CommandFlag.READONLY)); + builder.register("GEODIST", EnumSet.of(CommandFlag.READONLY)); + builder.register("GEOHASH", EnumSet.of(CommandFlag.READONLY)); + builder.register("GEOPOS", EnumSet.of(CommandFlag.READONLY)); + builder.register("GEORADIUSBYMEMBER_RO", EnumSet.of(CommandFlag.READONLY)); + builder.register("GEORADIUS_RO", EnumSet.of(CommandFlag.READONLY)); + builder.register("GEOSEARCH", EnumSet.of(CommandFlag.READONLY)); + builder.register("GETRANGE", EnumSet.of(CommandFlag.READONLY)); + builder.register("HGETALL", EnumSet.of(CommandFlag.READONLY)); + builder.register("HKEYS", EnumSet.of(CommandFlag.READONLY)); + builder.register("HRANDFIELD", EnumSet.of(CommandFlag.READONLY)); + builder.register("HSCAN", EnumSet.of(CommandFlag.READONLY)); + builder.register("HVALS", EnumSet.of(CommandFlag.READONLY)); + builder.register("KEYS", EnumSet.of(CommandFlag.READONLY)); + builder.register("LCS", EnumSet.of(CommandFlag.READONLY)); + builder.register("LINDEX", EnumSet.of(CommandFlag.READONLY)); + builder.register("LPOS", EnumSet.of(CommandFlag.READONLY)); + builder.register("LRANGE", EnumSet.of(CommandFlag.READONLY)); + builder.register("PFCOUNT", EnumSet.of(CommandFlag.READONLY)); + builder.register("RANDOMKEY", EnumSet.of(CommandFlag.READONLY)); + builder.register("SCAN", EnumSet.of(CommandFlag.READONLY)); + builder.register("SDIFF", EnumSet.of(CommandFlag.READONLY)); + builder.register("SINTER", EnumSet.of(CommandFlag.READONLY)); + builder.register("SMEMBERS", EnumSet.of(CommandFlag.READONLY)); + builder.register("SRANDMEMBER", EnumSet.of(CommandFlag.READONLY)); + builder.register("SSCAN", EnumSet.of(CommandFlag.READONLY)); + builder.register("SUBSTR", EnumSet.of(CommandFlag.READONLY)); + builder.register("SUNION", EnumSet.of(CommandFlag.READONLY)); + builder.register("XPENDING", EnumSet.of(CommandFlag.READONLY)); + builder.register("XRANGE", EnumSet.of(CommandFlag.READONLY)); + builder.register("XREVRANGE", EnumSet.of(CommandFlag.READONLY)); + builder.register("ZRANDMEMBER", EnumSet.of(CommandFlag.READONLY)); + builder.register("ZRANGE", EnumSet.of(CommandFlag.READONLY)); + builder.register("ZRANGEBYLEX", EnumSet.of(CommandFlag.READONLY)); + builder.register("ZRANGEBYSCORE", EnumSet.of(CommandFlag.READONLY)); + builder.register("ZREVRANGE", EnumSet.of(CommandFlag.READONLY)); + builder.register("ZREVRANGEBYLEX", EnumSet.of(CommandFlag.READONLY)); + builder.register("ZREVRANGEBYSCORE", EnumSet.of(CommandFlag.READONLY)); + builder.register("ZSCAN", EnumSet.of(CommandFlag.READONLY)); + + // 10 command(s) with: write + builder.register("DEL", EnumSet.of(CommandFlag.WRITE)); + builder.register("FLUSHALL", EnumSet.of(CommandFlag.WRITE)); + builder.register("FLUSHDB", EnumSet.of(CommandFlag.WRITE)); + builder.register("LREM", EnumSet.of(CommandFlag.WRITE)); + builder.register("LTRIM", EnumSet.of(CommandFlag.WRITE)); + builder.register("RENAME", EnumSet.of(CommandFlag.WRITE)); + builder.register("XTRIM", EnumSet.of(CommandFlag.WRITE)); + builder.register("ZREMRANGEBYLEX", EnumSet.of(CommandFlag.WRITE)); + builder.register("ZREMRANGEBYRANK", EnumSet.of(CommandFlag.WRITE)); + builder.register("ZREMRANGEBYSCORE", EnumSet.of(CommandFlag.WRITE)); + + // 2 command(s) with: blocking, write + builder.register("BLPOP", EnumSet.of(CommandFlag.BLOCKING, CommandFlag.WRITE)); + builder.register("BRPOP", EnumSet.of(CommandFlag.BLOCKING, CommandFlag.WRITE)); + + // 22 command(s) with: denyoom, write + builder.register("BITFIELD", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("BITOP", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("COPY", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("GEOADD", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("GEOSEARCHSTORE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("LINSERT", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("LMOVE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("LSET", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("MSET", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("MSETNX", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("PFMERGE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("PSETEX", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("RESTORE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("RPOPLPUSH", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("SDIFFSTORE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("SET", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("SETBIT", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("SETEX", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("SETRANGE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("SINTERSTORE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("SUNIONSTORE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("ZRANGESTORE", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.WRITE)); + + // 35 command(s) with: fast, readonly + builder.register("BITFIELD_RO", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("DBSIZE", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("EXISTS", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("EXPIRETIME", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("GET", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("GETBIT", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("HEXISTS", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("HEXPIRETIME", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("HGET", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("HLEN", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("HMGET", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("HPEXPIRETIME", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("HPTTL", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("HSTRLEN", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("HTTL", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("LLEN", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("LOLWUT", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("MGET", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("PEXPIRETIME", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("PTTL", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("SCARD", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("SISMEMBER", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("SMISMEMBER", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("STRLEN", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("TOUCH", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("TTL", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("TYPE", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("XLEN", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("ZCARD", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("ZCOUNT", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("ZLEXCOUNT", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("ZMSCORE", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("ZRANK", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("ZREVRANK", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + builder.register("ZSCORE", EnumSet.of(CommandFlag.FAST, CommandFlag.READONLY)); + + // 33 command(s) with: fast, write + builder.register("EXPIRE", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("EXPIREAT", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("GETDEL", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("GETEX", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HDEL", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HEXPIRE", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HEXPIREAT", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HGETDEL", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HGETEX", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HPERSIST", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HPEXPIRE", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HPEXPIREAT", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("LPOP", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("MOVE", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("PERSIST", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("PEXPIRE", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("PEXPIREAT", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("RENAMENX", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("RPOP", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("SMOVE", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("SPOP", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("SREM", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("SWAPDB", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("UNLINK", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("XACK", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("XACKDEL", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("XAUTOCLAIM", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("XCLAIM", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("XDEL", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("XDELEX", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("ZPOPMAX", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("ZPOPMIN", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("ZREM", EnumSet.of(CommandFlag.FAST, CommandFlag.WRITE)); + + // 1 command(s) with: loading, stale + builder.register("INFO", EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE)); + + // 55 command(s) with: module, readonly + builder.register("CMS.INFO", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("CMS.QUERY", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.AGGREGATE", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.CONFIG", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.CURSOR", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.DICTDUMP", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.EXPLAIN", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.EXPLAINCLI", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.GET", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.INFO", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.MGET", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.PROFILE", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.SEARCH", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.SPELLCHECK", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.SUGGET", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.SUGLEN", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.SYNDUMP", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT.TAGVALS", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("FT._LIST", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.ARRINDEX", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.ARRLEN", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.DEBUG", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.GET", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.MGET", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.OBJKEYS", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.OBJLEN", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.RESP", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.STRLEN", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("JSON.TYPE", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.BYRANK", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.BYREVRANK", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.CDF", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.INFO", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.MAX", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.MIN", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.QUANTILE", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.RANK", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.REVRANK", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TDIGEST.TRIMMED_MEAN", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TOPK.COUNT", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TOPK.INFO", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TOPK.LIST", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TOPK.QUERY", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TS.GET", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TS.INFO", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TS.MGET", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TS.MRANGE", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TS.MREVRANGE", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TS.QUERYINDEX", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TS.RANGE", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("TS.REVRANGE", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("VISMEMBER", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("VRANDMEMBER", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("VSIM", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("_FT.CONFIG", EnumSet.of(CommandFlag.MODULE, CommandFlag.READONLY)); + + // 20 command(s) with: module, write + builder.register("FT.ALIASDEL", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.DEL", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.DICTDEL", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.DROP", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.DROPINDEX", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.SUGDEL", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT._ALIASDELIFX", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT._DROPIFX", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT._DROPINDEXIFX", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.ARRPOP", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.ARRTRIM", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.CLEAR", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.DEL", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.FORGET", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.NUMINCRBY", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.NUMMULTBY", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.NUMPOWBY", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TS.DEL", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TS.DELETERULE", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("VREM", EnumSet.of(CommandFlag.MODULE, CommandFlag.WRITE)); + + // 6 command(s) with: movablekeys, readonly + builder.register("SINTERCARD", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY)); + builder.register("SORT_RO", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY)); + builder.register("ZDIFF", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY)); + builder.register("ZINTER", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY)); + builder.register("ZINTERCARD", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY)); + builder.register("ZUNION", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.READONLY)); + + // 3 command(s) with: movablekeys, write + builder.register("LMPOP", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + builder.register("MIGRATE", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + builder.register("ZMPOP", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + + // 1 command(s) with: admin, denyoom, write + builder.register("PFDEBUG", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.DENYOOM, CommandFlag.WRITE)); + + // 2 command(s) with: admin, noscript, no_async_loading + builder.register("BGREWRITEAOF", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING)); + builder.register("BGSAVE", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.NO_ASYNC_LOADING)); + + // 1 command(s) with: admin, noscript, stale + builder.register("FAILOVER", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + + // 1 command(s) with: asking, denyoom, write + builder.register("RESTORE-ASKING", + EnumSet.of(CommandFlag.ASKING, CommandFlag.DENYOOM, CommandFlag.WRITE)); + + // 2 command(s) with: blocking, denyoom, write + builder.register("BLMOVE", + EnumSet.of(CommandFlag.BLOCKING, CommandFlag.DENYOOM, CommandFlag.WRITE)); + builder.register("BRPOPLPUSH", + EnumSet.of(CommandFlag.BLOCKING, CommandFlag.DENYOOM, CommandFlag.WRITE)); + + // 2 command(s) with: blocking, fast, write + builder.register("BZPOPMAX", + EnumSet.of(CommandFlag.BLOCKING, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("BZPOPMIN", + EnumSet.of(CommandFlag.BLOCKING, CommandFlag.FAST, CommandFlag.WRITE)); + + // 1 command(s) with: blocking, movablekeys, readonly + builder.register("XREAD", + EnumSet.of(CommandFlag.BLOCKING, CommandFlag.MOVABLEKEYS, CommandFlag.READONLY)); + + // 3 command(s) with: blocking, movablekeys, write + builder.register("BLMPOP", + EnumSet.of(CommandFlag.BLOCKING, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + builder.register("BZMPOP", + EnumSet.of(CommandFlag.BLOCKING, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + builder.register("XREADGROUP", + EnumSet.of(CommandFlag.BLOCKING, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + + // 24 command(s) with: denyoom, fast, write + builder.register("APPEND", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("DECR", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("DECRBY", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("GETSET", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HINCRBY", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HINCRBYFLOAT", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HMSET", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HSET", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HSETEX", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("HSETNX", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("INCR", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("INCRBY", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("INCRBYFLOAT", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("LPUSH", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("LPUSHX", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("PFADD", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("RPUSH", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("RPUSHX", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("SADD", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("SETNX", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("XADD", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("XSETID", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("ZADD", EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + builder.register("ZINCRBY", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.FAST, CommandFlag.WRITE)); + + // 49 command(s) with: denyoom, module, write + builder.register("BF.ADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("BF.INSERT", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("BF.LOADCHUNK", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("BF.MADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("BF.RESERVE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CF.ADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CF.ADDNX", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CF.INSERT", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CF.INSERTNX", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CF.LOADCHUNK", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CF.RESERVE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CMS.INCRBY", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CMS.INITBYDIM", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CMS.INITBYPROB", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("CMS.MERGE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.ADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.ALIASADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.ALIASUPDATE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.ALTER", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.CREATE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.DICTADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.SUGADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.SYNADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT.SYNUPDATE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT._ALIASADDIFNX", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT._ALTERIFNX", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("FT._CREATEIFNX", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.ARRAPPEND", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.ARRINSERT", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.MERGE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.MSET", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.SET", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.STRAPPEND", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("JSON.TOGGLE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TDIGEST.ADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TDIGEST.CREATE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TDIGEST.MERGE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TDIGEST.RESET", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TOPK.ADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TOPK.INCRBY", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TOPK.RESERVE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TS.ADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TS.ALTER", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TS.CREATE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TS.DECRBY", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TS.INCRBY", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TS.MADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("VADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("_FT.SAFEADD", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MODULE, CommandFlag.WRITE)); + + // 6 command(s) with: denyoom, movablekeys, write + builder.register("GEORADIUS", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + builder.register("GEORADIUSBYMEMBER", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + builder.register("SORT", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + builder.register("ZDIFFSTORE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + builder.register("ZINTERSTORE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + builder.register("ZUNIONSTORE", + EnumSet.of(CommandFlag.DENYOOM, CommandFlag.MOVABLEKEYS, CommandFlag.WRITE)); + + // 6 command(s) with: fast, loading, stale + builder.register("ECHO", EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("LASTSAVE", + EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("READONLY", + EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("READWRITE", + EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("SELECT", + EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE)); + builder.register("TIME", EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.STALE)); + + // 19 command(s) with: fast, module, readonly + builder.register("BF.CARD", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("BF.DEBUG", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("BF.EXISTS", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("BF.INFO", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("BF.MEXISTS", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("BF.SCANDUMP", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("CF.COMPACT", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("CF.COUNT", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("CF.DEBUG", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("CF.EXISTS", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("CF.INFO", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("CF.MEXISTS", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("CF.SCANDUMP", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("VCARD", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("VDIM", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("VEMB", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("VGETATTR", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("VINFO", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + builder.register("VLINKS", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.READONLY)); + + // 3 command(s) with: fast, module, write + builder.register("CF.DEL", EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("TS.CREATERULE", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.WRITE)); + builder.register("VSETATTR", + EnumSet.of(CommandFlag.FAST, CommandFlag.MODULE, CommandFlag.WRITE)); + + // 3 command(s) with: module, noscript, readonly + builder.register("SEARCH.CLUSTERREFRESH", + EnumSet.of(CommandFlag.MODULE, CommandFlag.NOSCRIPT, CommandFlag.READONLY)); + builder.register("TIMESERIES.CLUSTERSET", + EnumSet.of(CommandFlag.MODULE, CommandFlag.NOSCRIPT, CommandFlag.READONLY)); + builder.register("TIMESERIES.REFRESHCLUSTER", + EnumSet.of(CommandFlag.MODULE, CommandFlag.NOSCRIPT, CommandFlag.READONLY)); + + // 2 command(s) with: admin, loading, noscript, stale + builder.register("DEBUG", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("MONITOR", + EnumSet.of(CommandFlag.ADMIN, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + + // 3 command(s) with: admin, noscript, no_async_loading, no_multi + builder.register("PSYNC", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, + CommandFlag.NO_ASYNC_LOADING, CommandFlag.NO_MULTI)); + builder.register("SAVE", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, + CommandFlag.NO_ASYNC_LOADING, CommandFlag.NO_MULTI)); + builder.register("SYNC", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, + CommandFlag.NO_ASYNC_LOADING, CommandFlag.NO_MULTI)); + + // 2 command(s) with: admin, noscript, no_async_loading, stale + builder.register("REPLICAOF", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, + CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + builder.register("SLAVEOF", EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT, + CommandFlag.NO_ASYNC_LOADING, CommandFlag.STALE)); + + // 1 command(s) with: fast, loading, noscript, stale + builder.register("ROLE", + EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + + // 2 command(s) with: fast, loading, pubsub, stale + builder.register("PUBLISH", + EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("SPUBLISH", + EnumSet.of(CommandFlag.FAST, CommandFlag.LOADING, CommandFlag.PUBSUB, CommandFlag.STALE)); + + // 2 command(s) with: loading, module, noscript, readonly + builder.register("SEARCH.CLUSTERINFO", EnumSet.of(CommandFlag.LOADING, CommandFlag.MODULE, + CommandFlag.NOSCRIPT, CommandFlag.READONLY)); + builder.register("SEARCH.CLUSTERSET", EnumSet.of(CommandFlag.LOADING, CommandFlag.MODULE, + CommandFlag.NOSCRIPT, CommandFlag.READONLY)); + + // 6 command(s) with: loading, noscript, pubsub, stale + builder.register("PSUBSCRIBE", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("PUNSUBSCRIBE", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("SSUBSCRIBE", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("SUBSCRIBE", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("SUNSUBSCRIBE", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE)); + builder.register("UNSUBSCRIBE", + EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.PUBSUB, CommandFlag.STALE)); + + // 1 command(s) with: loading, noscript, skip_slowlog, stale + builder.register("EXEC", EnumSet.of(CommandFlag.LOADING, CommandFlag.NOSCRIPT, + CommandFlag.SKIP_SLOWLOG, CommandFlag.STALE)); + + // 1 command(s) with: admin, allow_busy, loading, noscript, stale + builder.register("REPLCONF", EnumSet.of(CommandFlag.ADMIN, CommandFlag.ALLOW_BUSY, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + + // 4 command(s) with: allow_busy, fast, loading, noscript, stale + builder.register("DISCARD", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("MULTI", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("UNWATCH", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + builder.register("WATCH", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.STALE)); + + // 3 command(s) with: movablekeys, noscript, no_mandatory_keys, skip_monitor, stale + builder.register("EVAL", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT, + CommandFlag.NO_MANDATORY_KEYS, CommandFlag.SKIP_MONITOR, CommandFlag.STALE)); + builder.register("EVALSHA", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT, + CommandFlag.NO_MANDATORY_KEYS, CommandFlag.SKIP_MONITOR, CommandFlag.STALE)); + builder.register("FCALL", EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT, + CommandFlag.NO_MANDATORY_KEYS, CommandFlag.SKIP_MONITOR, CommandFlag.STALE)); + + // 1 command(s) with: admin, allow_busy, loading, noscript, no_multi, stale + builder.register("SHUTDOWN", EnumSet.of(CommandFlag.ADMIN, CommandFlag.ALLOW_BUSY, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_MULTI, CommandFlag.STALE)); + + // 4 command(s) with: allow_busy, fast, loading, noscript, no_auth, stale + builder.register("AUTH", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_AUTH, CommandFlag.STALE)); + builder.register("HELLO", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_AUTH, CommandFlag.STALE)); + builder.register("QUIT", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_AUTH, CommandFlag.STALE)); + builder.register("RESET", EnumSet.of(CommandFlag.ALLOW_BUSY, CommandFlag.FAST, + CommandFlag.LOADING, CommandFlag.NOSCRIPT, CommandFlag.NO_AUTH, CommandFlag.STALE)); + + // 3 command(s) with: movablekeys, noscript, no_mandatory_keys, readonly, skip_monitor, stale + builder.register("EVALSHA_RO", + EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT, CommandFlag.NO_MANDATORY_KEYS, + CommandFlag.READONLY, CommandFlag.SKIP_MONITOR, CommandFlag.STALE)); + builder.register("EVAL_RO", + EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT, CommandFlag.NO_MANDATORY_KEYS, + CommandFlag.READONLY, CommandFlag.SKIP_MONITOR, CommandFlag.STALE)); + builder.register("FCALL_RO", + EnumSet.of(CommandFlag.MOVABLEKEYS, CommandFlag.NOSCRIPT, CommandFlag.NO_MANDATORY_KEYS, + CommandFlag.READONLY, CommandFlag.SKIP_MONITOR, CommandFlag.STALE)); + + } + +} diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index 3839c38ea2..2f5103c0c8 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -172,21 +172,21 @@ public UnifiedJedis(Connection connection) { @Deprecated public UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) { - this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider, + this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration, StaticCommandFlagsRegistry.registry()), provider, new ClusterCommandObjects()); } @Deprecated protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, RedisProtocol protocol) { - this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider, + this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration, StaticCommandFlagsRegistry.registry()), provider, new ClusterCommandObjects(), protocol); } @Deprecated protected UnifiedJedis(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration, RedisProtocol protocol, Cache cache) { - this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider, + this(new ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration, StaticCommandFlagsRegistry.registry()), provider, new ClusterCommandObjects(), protocol, cache); } diff --git a/src/main/java/redis/clients/jedis/builders/ClusterClientBuilder.java b/src/main/java/redis/clients/jedis/builders/ClusterClientBuilder.java index d5ff9d9cd2..fd211a2ae6 100644 --- a/src/main/java/redis/clients/jedis/builders/ClusterClientBuilder.java +++ b/src/main/java/redis/clients/jedis/builders/ClusterClientBuilder.java @@ -24,6 +24,7 @@ public abstract class ClusterClientBuilder private Duration maxTotalRetriesDuration = Duration .ofMillis(JedisCluster.DEFAULT_TIMEOUT * JedisCluster.DEFAULT_MAX_ATTEMPTS); private Duration topologyRefreshPeriod = null; + private CommandFlagsRegistry commandFlags = null; /** * Sets the cluster nodes to connect to. @@ -78,6 +79,16 @@ public ClusterClientBuilder topologyRefreshPeriod(Duration topologyRefreshPer return this; } + /** + * Overrides the default command flags registry. + * @param commandFlags custom command flags registry + * @return this builder + */ + public ClusterClientBuilder commandFlags(CommandFlagsRegistry commandFlags) { + this.commandFlags = commandFlags; + return this; + } + @Override protected ClusterClientBuilder self() { return this; @@ -89,10 +100,22 @@ protected ConnectionProvider createDefaultConnectionProvider() { this.topologyRefreshPeriod); } + /** + * Creates a default command flags registry based on the current configuration. + * @return CommandFlagsRegistry + */ + protected CommandFlagsRegistry createDefaultCommandFlagsRegistry() { + return StaticCommandFlagsRegistry.registry(); + } + @Override protected CommandExecutor createDefaultCommandExecutor() { + if (this.commandFlags == null) { + this.commandFlags = createDefaultCommandFlagsRegistry(); + } + return new ClusterCommandExecutor((ClusterConnectionProvider) this.connectionProvider, - this.maxAttempts, this.maxTotalRetriesDuration); + this.maxAttempts, this.maxTotalRetriesDuration, this.commandFlags); } @Override diff --git a/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java b/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java index e475a40948..4e7b3fe277 100644 --- a/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java +++ b/src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java @@ -8,11 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import redis.clients.jedis.CommandObject; -import redis.clients.jedis.Connection; -import redis.clients.jedis.ConnectionPool; -import redis.clients.jedis.HostAndPort; -import redis.clients.jedis.Protocol; +import redis.clients.jedis.*; import redis.clients.jedis.annots.VisibleForTesting; import redis.clients.jedis.exceptions.*; import redis.clients.jedis.providers.ClusterConnectionProvider; @@ -26,9 +22,21 @@ public class ClusterCommandExecutor implements CommandExecutor { public final ClusterConnectionProvider provider; protected final int maxAttempts; protected final Duration maxTotalRetriesDuration; + protected final CommandFlagsRegistry flags; + /** + * @deprecated use {@link #ClusterCommandExecutor(ClusterConnectionProvider, int, Duration, CommandFlagsRegistry)} + * instead. This constructor will be removed in the next major version. + */ + @Deprecated public ClusterCommandExecutor(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) { + this(provider, maxAttempts, maxTotalRetriesDuration, StaticCommandFlagsRegistry.registry()); + } + + public ClusterCommandExecutor(ClusterConnectionProvider provider, int maxAttempts, + Duration maxTotalRetriesDuration, CommandFlagsRegistry flags) { + JedisAsserts.notNull(flags, "CommandFlagsRegistry must not be null"); JedisAsserts.notNull(provider, "provider must not be null"); JedisAsserts.isTrue(maxAttempts > 0, "maxAttempts must be greater than 0"); JedisAsserts.notNull(maxTotalRetriesDuration, "maxTotalRetriesDuration must not be null"); @@ -36,6 +44,7 @@ public ClusterCommandExecutor(ClusterConnectionProvider provider, int maxAttempt this.provider = provider; this.maxAttempts = maxAttempts; this.maxTotalRetriesDuration = maxTotalRetriesDuration; + this.flags = flags; } @Override diff --git a/src/main/java/redis/clients/jedis/util/JedisByteHashMap.java b/src/main/java/redis/clients/jedis/util/JedisByteHashMap.java index 58055b0896..1adfc69dd8 100644 --- a/src/main/java/redis/clients/jedis/util/JedisByteHashMap.java +++ b/src/main/java/redis/clients/jedis/util/JedisByteHashMap.java @@ -95,13 +95,16 @@ public Collection values() { } private static final class ByteArrayWrapper implements Serializable { + private static final long serialVersionUID = 1L; private final byte[] data; + private final int hashCode; public ByteArrayWrapper(byte[] data) { if (data == null) { throw new NullPointerException(); } this.data = data; + this.hashCode = Arrays.hashCode(data); } @Override @@ -115,7 +118,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Arrays.hashCode(data); + return hashCode; } } diff --git a/src/main/java/redis/clients/jedis/util/JedisByteMap.java b/src/main/java/redis/clients/jedis/util/JedisByteMap.java index 5dfff673da..8abdee3342 100644 --- a/src/main/java/redis/clients/jedis/util/JedisByteMap.java +++ b/src/main/java/redis/clients/jedis/util/JedisByteMap.java @@ -89,13 +89,16 @@ public Collection values() { } private static final class ByteArrayWrapper implements Serializable { + private static final long serialVersionUID = 1L; private final byte[] data; + private final int hashCode; public ByteArrayWrapper(byte[] data) { if (data == null) { throw new NullPointerException(); } this.data = data; + this.hashCode = Arrays.hashCode(data); } @Override @@ -109,7 +112,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Arrays.hashCode(data); + return hashCode; } } diff --git a/src/main/java/redis/clients/jedis/util/SafeEncoder.java b/src/main/java/redis/clients/jedis/util/SafeEncoder.java index fb5d8fb24e..78338ea2e5 100644 --- a/src/main/java/redis/clients/jedis/util/SafeEncoder.java +++ b/src/main/java/redis/clients/jedis/util/SafeEncoder.java @@ -63,4 +63,32 @@ public static Object encodeObject(Object dataToEncode) { return dataToEncode; } + + /** + * Converts a byte array to uppercase by converting lowercase ASCII letters (a-z) to uppercase (A-Z). + * This method is optimized for ASCII text and performs direct byte manipulation, which is significantly + * faster than converting to String and calling String.toUpperCase(). + *

+ * This method only works correctly for ASCII text. Non-ASCII characters are left unchanged. + * For Redis command names (which are always ASCII), this is safe and provides ~47% performance + * improvement over the String-based approach. + * + * @param data the byte array to convert to uppercase + * @return a new byte array with lowercase ASCII letters converted to uppercase + */ + public static byte[] toUpperCase(final byte[] data) { + if (data == null) { + return null; + } + + byte[] uppercaseBytes = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + if (data[i] >= 'a' && data[i] <= 'z') { + uppercaseBytes[i] = (byte) (data[i] - 32); + } else { + uppercaseBytes[i] = data[i]; + } + } + return uppercaseBytes; + } } diff --git a/src/test/java/redis/clients/jedis/ClusterCommandExecutorTest.java b/src/test/java/redis/clients/jedis/ClusterCommandExecutorTest.java index 0ff04a832a..955dfff642 100644 --- a/src/test/java/redis/clients/jedis/ClusterCommandExecutorTest.java +++ b/src/test/java/redis/clients/jedis/ClusterCommandExecutorTest.java @@ -37,7 +37,8 @@ public class ClusterCommandExecutorTest { @Test public void runSuccessfulExecute() { ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class); - ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO) { + ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, Duration.ZERO, + StaticCommandFlagsRegistry.registry()) { @Override public T execute(Connection connection, CommandObject commandObject) { return (T) "foo"; @@ -53,7 +54,8 @@ protected void sleep(long ignored) { @Test public void runFailOnFirstExecSuccessOnSecondExec() { ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class); - ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND) { + ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND, + StaticCommandFlagsRegistry.registry()) { boolean isFirstCall = true; @Override @@ -79,7 +81,8 @@ protected void sleep(long ignored) { public void runAlwaysFailing() { ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class); final LongConsumer sleep = mock(LongConsumer.class); - ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 3, ONE_SECOND) { + ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 3, ONE_SECOND, + StaticCommandFlagsRegistry.registry()) { @Override public T execute(Connection connection, CommandObject commandObject) { throw new JedisConnectionException("Connection failed"); @@ -109,7 +112,8 @@ protected void sleep(long sleepMillis) { public void runMovedSuccess() { ClusterConnectionProvider connectionHandler = mock(ClusterConnectionProvider.class); final HostAndPort movedTarget = new HostAndPort(null, 0); - ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND) { + ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND, + StaticCommandFlagsRegistry.registry()) { boolean isFirstCall = true; @Override @@ -146,7 +150,8 @@ public void runAskSuccess() { final HostAndPort askTarget = new HostAndPort(null, 0); when(connectionHandler.getConnection(askTarget)).thenReturn(connection); - ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND) { + ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, ONE_SECOND, + StaticCommandFlagsRegistry.registry()) { boolean isFirstCall = true; @Override @@ -199,7 +204,8 @@ public void runMovedThenAllNodesFailing() { final LongConsumer sleep = mock(LongConsumer.class); final HostAndPort movedTarget = new HostAndPort(null, 0); - ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 5, ONE_SECOND) { + ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 5, ONE_SECOND, + StaticCommandFlagsRegistry.registry()) { @Override public T execute(Connection connection, CommandObject commandObject) { if (redirecter == connection) { @@ -266,7 +272,8 @@ public void runMasterFailingReplicaRecovering() { }).when(connectionHandler).renewSlotCache(); final AtomicLong totalSleepMs = new AtomicLong(); - ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 5, ONE_SECOND) { + ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 5, ONE_SECOND, + StaticCommandFlagsRegistry.registry()) { @Override public T execute(Connection connection, CommandObject commandObject) { @@ -304,7 +311,7 @@ public void runRethrowsJedisNoReachableClusterNodeException() { JedisClusterOperationException.class); ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 10, - Duration.ZERO) { + Duration.ZERO, StaticCommandFlagsRegistry.registry()) { @Override public T execute(Connection connection, CommandObject commandObject) { return null; @@ -325,7 +332,8 @@ public void runStopsRetryingAfterTimeout() { //final LongConsumer sleep = mock(LongConsumer.class); final AtomicLong totalSleepMs = new AtomicLong(); - ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 3, Duration.ZERO) { + ClusterCommandExecutor testMe = new ClusterCommandExecutor(connectionHandler, 3, Duration.ZERO, + StaticCommandFlagsRegistry.registry()) { @Override public T execute(Connection connection, CommandObject commandObject) { try { diff --git a/src/test/java/redis/clients/jedis/StaticCommandFlagsRegistryTest.java b/src/test/java/redis/clients/jedis/StaticCommandFlagsRegistryTest.java new file mode 100644 index 0000000000..122ed6272b --- /dev/null +++ b/src/test/java/redis/clients/jedis/StaticCommandFlagsRegistryTest.java @@ -0,0 +1,176 @@ +package redis.clients.jedis; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.EnumSet; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import redis.clients.jedis.CommandFlagsRegistry.CommandFlag; +import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.util.SafeEncoder; + +/** + * Unit tests for StaticCommandFlagsRegistry. Tests the retrieval of command flags for various Redis + * commands, including commands with subcommands. + */ +public class StaticCommandFlagsRegistryTest { + + private StaticCommandFlagsRegistry registry; + + @BeforeEach + public void setUp() { + registry = StaticCommandFlagsRegistry.registry(); + } + + /** + * Test that FUNCTION LOAD command returns the correct flags. FUNCTION LOAD should have: DENYOOM, + * NOSCRIPT, WRITE flags. + */ + @Test + public void testFunctionLoadCommandFlags() { + // Create a CommandArguments for "FUNCTION LOAD" + CommandArguments functionLoadArgs = new CommandArguments(Protocol.Command.FUNCTION).add("LOAD"); + + EnumSet flags = registry.getFlags(functionLoadArgs); + + assertNotNull(flags, "Flags should not be null"); + assertFalse(flags.isEmpty(), "FUNCTION LOAD should have flags"); + assertEquals(3, flags.size(), "FUNCTION LOAD should have exactly 3 flags"); + assertTrue(flags.contains(CommandFlag.DENYOOM), "FUNCTION LOAD should have DENYOOM flag"); + assertTrue(flags.contains(CommandFlag.NOSCRIPT), "FUNCTION LOAD should have NOSCRIPT flag"); + assertTrue(flags.contains(CommandFlag.WRITE), "FUNCTION LOAD should have WRITE flag"); + } + + /** + * Test that FUNCTION DELETE command returns the correct flags. FUNCTION DELETE should have: + * NOSCRIPT, WRITE flags. + */ + @Test + public void testFunctionDeleteCommandFlags() { + CommandArguments functionDeleteArgs = new CommandArguments(Protocol.Command.FUNCTION) + .add("DELETE"); + + EnumSet flags = registry.getFlags(functionDeleteArgs); + + assertNotNull(flags, "Flags should not be null"); + assertFalse(flags.isEmpty(), "FUNCTION DELETE should have flags"); + assertEquals(2, flags.size(), "FUNCTION DELETE should have exactly 2 flags"); + assertTrue(flags.contains(CommandFlag.NOSCRIPT), "FUNCTION DELETE should have NOSCRIPT flag"); + assertTrue(flags.contains(CommandFlag.WRITE), "FUNCTION DELETE should have WRITE flag"); + } + + /** + * Test other subcommand examples: ACL SETUSER + */ + @Test + public void testAclSetUserCommandFlags() { + CommandArguments aclSetUserArgs = new CommandArguments(Protocol.Command.ACL).add("SETUSER"); + + EnumSet flags = registry.getFlags(aclSetUserArgs); + + assertNotNull(flags, "Flags should not be null"); + assertFalse(flags.isEmpty(), "ACL SETUSER should have flags"); + assertTrue(flags.contains(CommandFlag.ADMIN), "ACL SETUSER should have ADMIN flag"); + assertTrue(flags.contains(CommandFlag.NOSCRIPT), "ACL SETUSER should have NOSCRIPT flag"); + } + + /** + * Test other subcommand examples: CONFIG GET + */ + @Test + public void testConfigGetCommandFlags() { + CommandArguments configGetArgs = new CommandArguments(Protocol.Command.CONFIG).add("GET"); + + EnumSet flags = registry.getFlags(configGetArgs); + + assertNotNull(flags, "Flags should not be null"); + assertFalse(flags.isEmpty(), "CONFIG GET should have flags"); + assertTrue(flags.contains(CommandFlag.ADMIN), "CONFIG GET should have ADMIN flag"); + assertTrue(flags.contains(CommandFlag.LOADING), "CONFIG GET should have LOADING flag"); + assertTrue(flags.contains(CommandFlag.STALE), "CONFIG GET should have STALE flag"); + } + + /** + * Test simple command without subcommands: GET + */ + @Test + public void testGetCommandFlags() { + CommandArguments getArgs = new CommandArguments(Protocol.Command.GET).add("key"); + + EnumSet flags = registry.getFlags(getArgs); + + assertNotNull(flags, "Flags should not be null"); + assertFalse(flags.isEmpty(), "GET should have flags"); + assertTrue(flags.contains(CommandFlag.READONLY), "GET should have READONLY flag"); + assertTrue(flags.contains(CommandFlag.FAST), "GET should have FAST flag"); + } + + /** + * Test simple command without subcommands: SET + */ + @Test + public void testSetCommandFlags() { + CommandArguments setArgs = new CommandArguments(Protocol.Command.SET).add("key").add("value"); + + EnumSet flags = registry.getFlags(setArgs); + + assertNotNull(flags, "Flags should not be null"); + assertFalse(flags.isEmpty(), "SET should have flags"); + assertTrue(flags.contains(CommandFlag.WRITE), "SET should have WRITE flag"); + assertTrue(flags.contains(CommandFlag.DENYOOM), "SET should have DENYOOM flag"); + } + + /** + * Test that unknown commands return empty flags + */ + @Test + public void testUnknownCommandReturnsEmptyFlags() { + ProtocolCommand unknownCommand = () -> SafeEncoder.encode("UNKNOWN_COMMAND_XYZ"); + CommandArguments unknownArgs = new CommandArguments(unknownCommand); + + EnumSet flags = registry.getFlags(unknownArgs); + + assertNotNull(flags, "Flags should not be null"); + assertTrue(flags.isEmpty(), "Unknown command should return empty flags"); + } + + /** + * Test case insensitivity - command names should be normalized to uppercase + */ + @Test + public void testCaseInsensitivity() { + ProtocolCommand functionCommand = () -> SafeEncoder.encode("function"); + CommandArguments functionLoadArgs = new CommandArguments(functionCommand).add("load"); + + EnumSet flags = registry.getFlags(functionLoadArgs); + + assertNotNull(flags, "Flags should not be null"); + assertFalse(flags.isEmpty(), "function load (lowercase) should have flags"); + assertEquals(3, flags.size(), "function load should have exactly 3 flags"); + assertTrue(flags.contains(CommandFlag.DENYOOM), "function load should have DENYOOM flag"); + assertTrue(flags.contains(CommandFlag.NOSCRIPT), "function load should have NOSCRIPT flag"); + assertTrue(flags.contains(CommandFlag.WRITE), "function load should have WRITE flag"); + } + + /** + * Test that unknown subcommands of parent commands fall back to parent command flags. If the + * parent command also doesn't exist, it should return empty flags. + */ + @Test + public void testUnknownSubcommandFallback() { + // Create a CommandArguments for "FUNCTION UNKNOWN_SUBCOMMAND" + // This subcommand doesn't exist, so it should fall back to "FUNCTION" parent flags + // Since "FUNCTION" parent has empty flags, it should return empty flags + CommandArguments unknownSubcommandArgs = new CommandArguments(Protocol.Command.FUNCTION) + .add("UNKNOWN_SUBCOMMAND"); + + EnumSet flags = registry.getFlags(unknownSubcommandArgs); + + assertNotNull(flags, "Flags should not be null"); + assertTrue(flags.isEmpty(), + "Unknown FUNCTION subcommand should return empty flags (parent flags)"); + } +} diff --git a/src/test/java/redis/clients/jedis/codegen/CommandFlagsRegistryGenerator.java b/src/test/java/redis/clients/jedis/codegen/CommandFlagsRegistryGenerator.java new file mode 100644 index 0000000000..009ef07d05 --- /dev/null +++ b/src/test/java/redis/clients/jedis/codegen/CommandFlagsRegistryGenerator.java @@ -0,0 +1,513 @@ +package redis.clients.jedis.codegen; + +import com.google.gson.Gson; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Module; +import redis.clients.jedis.exceptions.JedisConnectionException; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Code generator for StaticCommandFlagsRegistry. This generator connects to a Redis server, + * retrieves all command metadata using the COMMAND command, and automatically generates the + * StaticCommandFlagsRegistry class that implements CommandFlagsRegistry interface. + *

+ * Usage: + * + *

+ * java -cp ... redis.clients.jedis.codegen.CommandFlagsRegistryGenerator [host] [port]
+ * 
+ *

+ * Arguments: + *

    + *
  • host - Redis server hostname (default: localhost)
  • + *
  • port - Redis server port (default: 6379)
  • + *
+ *

+ * Note: This is a code generation tool and should NOT be executed as part of regular tests. + */ +public class CommandFlagsRegistryGenerator { + + private static final String JAVA_FILE = "src/main/java/redis/clients/jedis/StaticCommandFlagsRegistryInitializer.java"; + private static final String BACKUP_JSON_FILE = "redis_commands_flags.json"; + + private final String redisHost; + private final int redisPort; + + // Server metadata collected during generation + private ServerMetadata serverMetadata; + + // Map JSON flag names to Java enum names + private static final Map FLAG_MAPPING = new LinkedHashMap<>(); + static { + FLAG_MAPPING.put("readonly", "READONLY"); + FLAG_MAPPING.put("write", "WRITE"); + FLAG_MAPPING.put("denyoom", "DENYOOM"); + FLAG_MAPPING.put("admin", "ADMIN"); + FLAG_MAPPING.put("pubsub", "PUBSUB"); + FLAG_MAPPING.put("noscript", "NOSCRIPT"); + FLAG_MAPPING.put("random", "RANDOM"); + FLAG_MAPPING.put("sort_for_script", "SORT_FOR_SCRIPT"); + FLAG_MAPPING.put("loading", "LOADING"); + FLAG_MAPPING.put("stale", "STALE"); + FLAG_MAPPING.put("skip_monitor", "SKIP_MONITOR"); + FLAG_MAPPING.put("skip_slowlog", "SKIP_SLOWLOG"); + FLAG_MAPPING.put("asking", "ASKING"); + FLAG_MAPPING.put("fast", "FAST"); + FLAG_MAPPING.put("movablekeys", "MOVABLEKEYS"); + FLAG_MAPPING.put("module", "MODULE"); + FLAG_MAPPING.put("blocking", "BLOCKING"); + FLAG_MAPPING.put("no_auth", "NO_AUTH"); + FLAG_MAPPING.put("no_async_loading", "NO_ASYNC_LOADING"); + FLAG_MAPPING.put("no_multi", "NO_MULTI"); + FLAG_MAPPING.put("no_mandatory_keys", "NO_MANDATORY_KEYS"); + FLAG_MAPPING.put("allow_busy", "ALLOW_BUSY"); + } + + public CommandFlagsRegistryGenerator(String host, int port) { + this.redisHost = host; + this.redisPort = port; + } + + public static void main(String[] args) { + printLine(); + System.out.println("StaticCommandFlagsRegistry Generator"); + printLine(); + + // Parse command line arguments + String host = args.length > 0 ? args[0] : "localhost"; + int port = args.length > 1 ? Integer.parseInt(args[1]) : 6379; + + System.out.println("Redis server: " + host + ":" + port); + System.out.println(); + + try { + CommandFlagsRegistryGenerator generator = new CommandFlagsRegistryGenerator(host, port); + generator.generate(); + + System.out.println(); + printLine(); + System.out.println("✓ Code generation completed successfully!"); + printLine(); + } catch (Exception e) { + System.err.println(); + printLine(); + System.err.println("✗ Code generation failed!"); + printLine(); + e.printStackTrace(); + System.exit(1); + } + } + + private static void printLine() { + for (int i = 0; i < 80; i++) { + System.out.print("="); + } + System.out.println(); + } + + public void generate() throws IOException { + Map> commandsFlags; + + // Step 1: Retrieve commands from Redis + System.out.println("\nStep 1: Connecting to Redis at " + redisHost + ":" + redisPort + "..."); + try { + commandsFlags = retrieveCommandsFromRedis(); + System.out.println("✓ Retrieved " + commandsFlags.size() + " commands from Redis"); + + // Save to backup JSON file + saveToJsonFile(commandsFlags); + } catch (JedisConnectionException e) { + System.err.println("✗ Failed to connect to Redis: " + e.getMessage()); + System.out.println("\nAttempting to use backup JSON file: " + BACKUP_JSON_FILE); + commandsFlags = readJsonFile(); + System.out.println("✓ Loaded " + commandsFlags.size() + " commands from backup file"); + } + + // Step 2: Process commands and group by flag combinations + System.out.println("\nStep 2: Processing commands and grouping by flags..."); + Map> flagCombinations = groupByFlags(commandsFlags); + System.out.println("✓ Found " + flagCombinations.size() + " unique flag combinations"); + + // Step 3: Generate StaticCommandFlagsRegistry class + System.out.println("\nStep 3: Generating StaticCommandFlagsRegistry class..."); + String classContent = generateRegistryClass(flagCombinations); + System.out.println("✓ Generated " + classContent.split("\n").length + " lines of code"); + + // Step 4: Write StaticCommandFlagsRegistry.java + System.out.println("\nStep 4: Writing " + JAVA_FILE + "..."); + writeJavaFile(classContent); + System.out.println("✓ Successfully created StaticCommandFlagsRegistry.java"); + } + + private Map> retrieveCommandsFromRedis() { + Map> result = new LinkedHashMap<>(); + + try (Jedis jedis = new Jedis(redisHost, redisPort)) { + jedis.connect(); + + // Collect server metadata + String infoServer = jedis.info("server"); + String version = extractInfoValue(infoServer, "redis_version"); + String mode = extractInfoValue(infoServer, "redis_mode"); + + // Get loaded modules + List modules = new ArrayList<>(); + try { + List moduleList = jedis.moduleList(); + for (Module module : moduleList) { + modules.add(module.getName()); + } + } catch (Exception e) { + // Module list might not be available in all Redis versions + System.out.println(" Note: Could not retrieve module list: " + e.getMessage()); + } + + serverMetadata = new ServerMetadata(version, mode, modules); + + // Get all commands using COMMAND + Map commands = jedis.command(); + + for (Map.Entry entry : commands.entrySet()) { + redis.clients.jedis.resps.CommandInfo cmdInfo = entry.getValue(); + String commandName = normalizeCommandName(cmdInfo.getName()); + + // Get flags + List flags = new ArrayList<>(); + if (cmdInfo.getFlags() != null) { + for (String flag : cmdInfo.getFlags()) { + flags.add(flag.toLowerCase()); + } + } + + // Check for subcommands + Map subcommands = cmdInfo.getSubcommands(); + + if (subcommands != null && !subcommands.isEmpty()) { + // This command has subcommands - process them instead of the parent + for (Map.Entry subEntry : subcommands + .entrySet()) { + redis.clients.jedis.resps.CommandInfo subCmdInfo = subEntry.getValue(); + String subCommandName = normalizeCommandName(subCmdInfo.getName()); + + // Filter out unwanted commands + if (shouldExcludeCommand(subCommandName)) { + continue; + } + + // Get subcommand flags + List subFlags = new ArrayList<>(); + if (subCmdInfo.getFlags() != null) { + for (String flag : subCmdInfo.getFlags()) { + subFlags.add(flag.toLowerCase()); + } + } + + result.put(subCommandName, subFlags); + } + } else { + // Regular command without subcommands + // Filter out unwanted commands + if (!shouldExcludeCommand(commandName)) { + result.put(commandName, flags); + } + } + } + + } + // Ignore close errors + + return result; + } + + /** + * Normalize command name: replace pipe separators with spaces and convert to uppercase. Redis + * returns command names like "acl|help" but Jedis uses "ACL HELP". + */ + private String normalizeCommandName(String commandName) { + return commandName.replace('|', ' ').toUpperCase(); + } + + /** + * Check if a command should be excluded from the registry. + *

+ * Exclusion rules: + *

    + *
  • All HELP subcommands (e.g., "ACL HELP", "CONFIG HELP", "XINFO HELP")
  • + *
  • All FT.DEBUG subcommands (e.g., "FT.DEBUG DUMP_TERMS", "FT.DEBUG GIT_SHA")
  • + *
  • All _FT.DEBUG subcommands (internal RediSearch debug commands)
  • + *
+ */ + private boolean shouldExcludeCommand(String commandName) { + // Exclude all HELP subcommands + if (commandName.endsWith(" HELP")) { + return true; + } + + // Exclude FT.DEBUG and _FT.DEBUG subcommands + return commandName.startsWith("FT.DEBUG ") || commandName.startsWith("_FT.DEBUG "); + } + + private String extractInfoValue(String info, String key) { + String[] lines = info.split("\n"); + for (String line : lines) { + if (line.startsWith(key + ":")) { + return line.substring(key.length() + 1).trim(); + } + } + return "unknown"; + } + + private void saveToJsonFile(Map> commandsFlags) throws IOException { + Gson gson = new Gson(); + String json = gson.toJson(commandsFlags); + + Path jsonPath = Paths.get(BACKUP_JSON_FILE); + Files.write(jsonPath, json.getBytes(StandardCharsets.UTF_8)); + System.out.println("✓ Saved backup to " + BACKUP_JSON_FILE); + } + + private Map> readJsonFile() throws IOException { + Path jsonPath = Paths.get(BACKUP_JSON_FILE); + if (!Files.exists(jsonPath)) { + throw new IOException("Backup file not found: " + BACKUP_JSON_FILE); + } + + // JDK 8 compatible: read file as bytes and convert to string + byte[] bytes = Files.readAllBytes(jsonPath); + String jsonContent = new String(bytes, StandardCharsets.UTF_8); + + Gson gson = new Gson(); + + // Parse JSON manually to preserve order + @SuppressWarnings("unchecked") + Map> parsed = gson.fromJson(jsonContent, Map.class); + + return new LinkedHashMap<>(parsed); + } + + private Map> groupByFlags(Map> commandsFlags) { + Map> result = new LinkedHashMap<>(); + + for (Map.Entry> entry : commandsFlags.entrySet()) { + String command = entry.getKey(); + List jsonFlags = entry.getValue(); + + // Convert JSON flags to Java enum names and sort + List javaFlags = jsonFlags.stream().map(f -> FLAG_MAPPING.get(f.toLowerCase())) + .filter(Objects::nonNull).sorted().collect(Collectors.toList()); + + FlagSet flagSet = new FlagSet(javaFlags); + result.computeIfAbsent(flagSet, k -> new ArrayList<>()).add(command.toUpperCase()); + } + + return result; + } + + private String generateRegistryClass(Map> flagCombinations) { + StringBuilder sb = new StringBuilder(); + + // Package and imports + sb.append("package redis.clients.jedis;\n\n"); + sb.append("import java.util.EnumSet;\n"); + sb.append("import static redis.clients.jedis.StaticCommandFlagsRegistry.EMPTY_FLAGS;\n"); + sb.append("import static redis.clients.jedis.CommandFlagsRegistry.CommandFlag;\n"); + + // Class javadoc + sb.append("/**\n"); + sb.append( + " * Static implementation of CommandFlagsRegistry. This class is auto-generated by\n"); + sb.append(" * CommandFlagsRegistryGenerator. DO NOT EDIT MANUALLY.\n"); + + // Add server metadata if available + if (serverMetadata != null) { + sb.append(" *

Generated from Redis Server:\n"); + sb.append(" *

    \n"); + sb.append(" *
  • Version: ").append(serverMetadata.version).append("
  • \n"); + sb.append(" *
  • Mode: ").append(serverMetadata.mode).append("
  • \n"); + if (!serverMetadata.modules.isEmpty()) { + sb.append(" *
  • Loaded Modules: ").append(String.join(", ", serverMetadata.modules)) + .append("
  • \n"); + } else { + sb.append(" *
  • Loaded Modules: none
  • \n"); + } + sb.append(" *
  • Generated at: ").append(serverMetadata.generatedAt).append("
  • \n"); + sb.append(" *
\n"); + } + + sb.append(" */\n"); + sb.append("final class StaticCommandFlagsRegistryInitializer {\n\n"); + + // Static initializer block + sb.append(" static void initialize(StaticCommandFlagsRegistry.Builder builder) {\n"); + + // Organize commands into parent commands and simple commands + Map> parentCommands = new LinkedHashMap<>(); + Map simpleCommands = new LinkedHashMap<>(); + + // Known parent commands + Set knownParents = new HashSet<>( + Arrays.asList("ACL", "CLIENT", "CLUSTER", "COMMAND", "CONFIG", "FUNCTION", "LATENCY", + "MEMORY", "MODULE", "OBJECT", "PUBSUB", "SCRIPT", "SLOWLOG", "XGROUP", "XINFO")); + + // Categorize commands + for (Map.Entry> entry : flagCombinations.entrySet()) { + FlagSet flagSet = entry.getKey(); + for (String command : entry.getValue()) { + int spaceIndex = command.indexOf(' '); + if (spaceIndex > 0) { + // This is a compound command (e.g., "FUNCTION LOAD") + String parent = command.substring(0, spaceIndex); + String subcommand = command.substring(spaceIndex + 1); + + if (knownParents.contains(parent)) { + parentCommands.computeIfAbsent(parent, k -> new LinkedHashMap<>()).put(subcommand, + flagSet); + } else { + // Not a known parent, treat as simple command + simpleCommands.put(command, flagSet); + } + } else { + // Simple command without subcommands + simpleCommands.put(command, flagSet); + } + } + } + + // Generate parent command registries + for (String parent : knownParents) { + sb.append(String.format("builder.register(\"%s\", EMPTY_FLAGS);", parent)); + + Map subcommands = parentCommands.get(parent); + if (subcommands != null && !subcommands.isEmpty()) { + sb.append(String.format(" // %s parent command with subcommands\n", parent)); + // Add subcommands + List sortedSubcommands = new ArrayList<>(subcommands.keySet()); + Collections.sort(sortedSubcommands); + + for (String subcommand : sortedSubcommands) { + FlagSet flagSet = subcommands.get(subcommand); + String enumSetExpr = createEnumSetExpression(flagSet.flags); + sb.append(String.format("builder.register(\"%s\", \"%s\", %s);\n", parent, subcommand, + enumSetExpr)); + } + } + } + + // Generate simple commands grouped by flags + Map> simpleCommandsByFlags = new LinkedHashMap<>(); + for (Map.Entry entry : simpleCommands.entrySet()) { + simpleCommandsByFlags.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()) + .add(entry.getKey()); + } + + // Sort by flag count, then alphabetically + List>> sortedEntries = simpleCommandsByFlags.entrySet().stream() + .sorted( + Comparator.comparing((Map.Entry> e) -> e.getKey().flags.size()) + .thenComparing(e -> e.getKey().toString())) + .collect(Collectors.toList()); + + for (Map.Entry> entry : sortedEntries) { + FlagSet flagSet = entry.getKey(); + List commands = entry.getValue(); + Collections.sort(commands); + + // Add comment + String flagDesc = flagSet.flags.isEmpty() ? "no flags" + : flagSet.flags.stream().map(String::toLowerCase).collect(Collectors.joining(", ")); + sb.append(String.format(" // %d command(s) with: %s\n", commands.size(), flagDesc)); + + // Generate EnumSet expression + String enumSetExpr = createEnumSetExpression(flagSet.flags); + + // Add registry entries using SafeEncoder.encode() + for (String command : commands) { + sb.append(String.format("builder.register(\"%s\", %s);\n", command, enumSetExpr)); + } + sb.append("\n"); + } + + // Close initializer block + sb.append(" }\n\n"); + + // Close class + sb.append("}\n"); + + return sb.toString(); + } + + private String createEnumSetExpression(List flags) { + if (flags.isEmpty()) { + return "EMPTY_FLAGS"; + } else if (flags.size() == 1) { + return "EnumSet.of(CommandFlag." + flags.get(0) + ")"; + } else { + String flagsList = flags.stream().map(f -> "CommandFlag." + f) + .collect(Collectors.joining(", ")); + return "EnumSet.of(" + flagsList + ")"; + } + } + + private void writeJavaFile(String classContent) throws IOException { + Path javaPath = Paths.get(JAVA_FILE); + + // JDK 8 compatible: write string as bytes + Files.write(javaPath, classContent.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Represents a set of flags for grouping commands + */ + private static class FlagSet { + final List flags; + final int hashCode; + + FlagSet(List flags) { + this.flags = new ArrayList<>(flags); + this.hashCode = this.flags.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FlagSet)) return false; + FlagSet flagSet = (FlagSet) o; + return flags.equals(flagSet.flags); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public String toString() { + return flags.toString(); + } + } + + /** + * Holds metadata about the Redis server used for generation + */ + private static class ServerMetadata { + final String version; + final String mode; + final List modules; + final String generatedAt; + + ServerMetadata(String version, String mode, List modules) { + this.version = version; + this.mode = mode; + this.modules = modules; + this.generatedAt = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss z") + .format(new java.util.Date()); + } + } +} diff --git a/src/test/java/redis/clients/jedis/codegen/README.md b/src/test/java/redis/clients/jedis/codegen/README.md new file mode 100644 index 0000000000..472e7103a4 --- /dev/null +++ b/src/test/java/redis/clients/jedis/codegen/README.md @@ -0,0 +1,50 @@ +# Code Generators + +This package contains code generation tools for the Jedis project. These are **not tests** and should not be executed as part of the test suite. + +## CommandFlagsRegistryGenerator + +Automatically generates and updates the static flags registry in `CommandObject.java` by retrieving command metadata from a running Redis server. + +### Purpose + +The `CommandObject` class uses a static registry to map Redis command names to their flags. + +### How It Works + +1. **Connects** to a Redis server (default: localhost:6379) +2. **Retrieves** all command metadata using the `COMMAND` command +3. **Processes** commands and subcommands, extracting their flags +4. **Groups** commands by their flag combinations +5. **Generates** a static initializer block with inline `EnumSet` creation +6. **Updates** `CommandObject.java` automatically using regex pattern matching +7. **Saves** a backup JSON file for offline use + +### Prerequisites + +- A running Redis server (version 7.0+ recommended for full command metadata) +- The Redis server should have all modules loaded if you want to include module commands + +### When to Run + +Run this generator whenever: +- Upgrading to a new Redis version +- New Redis modules are added to your server +- Command flags are modified in Redis +- You want to ensure the registry is up-to-date with your Redis server + +### Fallback Mode + +If the generator cannot connect to Redis, it will automatically fall back to using the backup JSON file (`redis_commands_flags.json`) if available. + +### Output + +The generator will: +- ✓ Connect to Redis and retrieve command metadata +- ✓ Process commands and subcommands +- ✓ Group commands by flag combinations +- ✓ Generate the complete static initializer block +- ✓ Update `src/main/java/redis/clients/jedis/CommandObject.java` in-place +- ✓ Save a backup JSON file for offline use +- ✓ Preserve original command names (with spaces, dots, hyphens) +