Repository: cunla/fakeredis-py (language: python)

Issue:
Support hash key expiration functions
**Is your feature request related to a problem? Please describe.**
I'm trying to use `fakeredis` to mock interactions that use `hexpireat` and `httl` which are not presently implemented.

**Describe the solution you'd like**
It would be great if those commands were implemented.

**Describe alternatives you've considered**
I took a quick look to see if it was obvious how to submit a PR for it and read [the docs](https://fakeredis.readthedocs.io/en/latest/guides/implement-command/) but given it looks like the `Hash` type just wraps dict and doesn't have an additional structure to manage key lifetimes, it wasn't immediately obvious.

**Additional context**
If you can give me a basic map for how to implement such a feature, I'd be happy to try my hand at it, but having only glanced at the codebase, I would need a sherpa.

<!-- POLAR PLEDGE BADGE START -->
## Upvote & Fund

- We're using [Polar.sh](https://polar.sh/cunla) so you can upvote and help fund this issue.
- We receive the funding once the issue is completed & confirmed by you.
- Thank you in advance for helping prioritize & fund our backlog.

<a href="https://polar.sh/cunla/fakeredis-py/issues/321">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/cunla/fakeredis-py/issues/321/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/cunla/fakeredis-py/issues/321/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->



Discussion / hints:
Hi, thanks for reporting and for offering to implement support for these.

Here is some info to help you implement:

- It should be implemented in `hash_mixin.py`. See `hrandfield` as an example for using the `@command` decorator since it has all the types as well.
- I would look at how `ttl` and `expireat` are implemented in `generic_mixin.py`
- Note `CommandItem` has the `value` property which is a `Hash` type for hash commands, but other values as well.

Otherwise, please also
- ensure you support all optional command arguments as well (Look into the `expireat`/`ttl` implementation, it should be pretty similar).
- update the documentation to make it clearer (I wrote it hoping it is clear, but obviously if it wasn't clear to you, I could have done a better job :smile: ).
- Add tests for these commands in `test_hash_commands.py`. There can be similar tests to `expireat`/`ttl` which exist in `test_generic_commands.py`.

Let me know if you need more help. I am happy to support any contribution.
Thanks! That all makes perfect sense.

The one thing I'd appreciate knowing your preference on: `key.value` in the context of one the `@command` decorated functions is a `Hash` - which is a wrapper around `dict` it looks like. I would need to store additional data for the timestamps - would your preference be to define a nested dictionary on `Hash` for that purpose, so I'd reference `key.value.expirations` or something like that?
Oh, interesting, I hadn't realized that HEXPIREAT supports expiration of specific fields within the hash. In that case, like you suggested, the `Hash` type needs to be changed to support this.

To make it simple, I would add the `expirations` field to the `Hash` type, and work out something like this:

```python
class Hash(dict):  # type:ignore
    DECODE_ERROR = msgs.INVALID_HASH_MSG
    redis_type = b"hash"

    def __init__(self):
        super().__init__()
        self.expirations: Dict[bytes, int] = {}

    def _check_expire(self, key: bytes) -> None:
        if key in self.expirations and self.expirations[key] < int(time.time()):
            del self[key]
            del self.expirations[key]

    def __get__(self, key: bytes) -> Any:
        self._check_expire(key)
        if key in self:
            return self[key]
        return self.__get__(key)

    def __contains__(self, item):
        self._check_expire(item)
        return super().__contains__(item)

    # more dict methods???
```

This should automatically make `HGET`/`HEXISTS`/... support the expiration.
Word. That makes total sense. I'll see what I can throw together. Thank you!
@j00bar, have you already started working on this? I am asking since I realized there are [a few new commands that are related](https://fakeredis.readthedocs.io/en/latest/redis-commands/Redis/#unsupported-hash-commands) and should be implemented in fakeredis.

I considered it might be beneficial to break the work into several parts:

1. Implement support for expiration on fields in `Hash` class.

Then, for every new command (`HEXPIRE`, `HEXPIREAT`, `HEXPIRETIME`, ...):

2. Implement new Hash command.
3. Implement tests for Hash command.

Depending on what progress you made, I figured let's deliver (1) quickly and then you can implement `hexpireat` and `httl` and I can implement other commands.

I've got an implementation in place - just writing tests for the moment. Happy to push the WIP if you'd like to look?
Sure, create a draft PR and I will review.

Fix patch:
diff --git a/fakeredis/_basefakesocket.py b/fakeredis/_basefakesocket.py
index bcca0a0a..10894800 100644
--- a/fakeredis/_basefakesocket.py
+++ b/fakeredis/_basefakesocket.py
@@ -9,7 +9,7 @@
 
 from . import _msgs as msgs
 from ._command_args_parsing import extract_args
-from ._commands import Int, Float, SUPPORTED_COMMANDS, COMMANDS_WITH_SUB, Item, Signature, CommandItem
+from ._commands import Int, Float, SUPPORTED_COMMANDS, COMMANDS_WITH_SUB, Item, Signature, CommandItem, Hash
 from ._helpers import (
     SimpleError,
     valid_response_type,
@@ -383,7 +383,7 @@ def _key_value_type(key: Item) -> SimpleString:
             return SimpleString(b"set")
         elif isinstance(key.value, ZSet):
             return SimpleString(b"zset")
-        elif isinstance(key.value, dict):
+        elif isinstance(key.value, Hash):
             return SimpleString(b"hash")
         elif isinstance(key.value, XStream):
             return SimpleString(b"stream")
diff --git a/fakeredis/_commands.py b/fakeredis/_commands.py
index 169b4d9f..467bb994 100644
--- a/fakeredis/_commands.py
+++ b/fakeredis/_commands.py
@@ -8,10 +8,10 @@
 import re
 import sys
 import time
-from typing import Tuple, Union, Optional, Any, Type, List, Callable, Sequence, Dict, Set
+from typing import Iterable, Tuple, Union, Optional, Any, Type, List, Callable, Sequence, Dict, Set
 
 from . import _msgs as msgs
-from ._helpers import null_terminate, SimpleError, Database
+from ._helpers import null_terminate, SimpleError, Database, current_time
 
 MAX_STRING_SIZE = 512 * 1024 * 1024
 SUPPORTED_COMMANDS: Dict[str, "Signature"] = dict()  # Dictionary of supported commands name => Signature
@@ -107,28 +107,80 @@ def __bool__(self) -> bool:
     __nonzero__ = __bool__  # For Python 2
 
 
-class Hash(dict):  # type:ignore
+class Hash:  # type:ignore
     DECODE_ERROR = msgs.INVALID_HASH_MSG
     redis_type = b"hash"
 
-    def __init__(self):
-        super().__init__()
-        self.expirations: Dict[bytes, int] = {}
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
+        super().__init__(*args, **kwargs)
+        self._expirations: Dict[bytes, int] = dict()
+        self._values: Dict[bytes, Any] = dict()
+
+    def _expire_keys(self):
+        removed = []
+        now = current_time()
+        for k in self._expirations:
+            if self._expirations[k] < now:
+                self._values.pop(k, None)
+                removed.append(k)
+        for k in removed:
+            self._expirations.pop(k, None)
+
+    def set_key_expireat(self, key: bytes, when_ms: int) -> int:
+        now = current_time()
+        if when_ms <= now:
+            self._values.pop(key, None)
+            self._expirations.pop(key, None)
+            return 2
+        self._expirations[key] = when_ms
+        return 1
+
+    def clear_key_expireat(self, key: bytes) -> bool:
+        return self._expirations.pop(key, None) is not None
+
+    def get_key_expireat(self, key: bytes) -> Optional[int]:
+        self._expire_keys()
+        return self._expirations.get(key, None)
+
+    def __getitem__(self, key: bytes) -> Any:
+        self._expire_keys()
+        return self._values.get(key)
+
+    def __contains__(self, key: bytes) -> bool:
+        self._expire_keys()
+        return self._values.__contains__(key)
+
+    def __setitem__(self, key: bytes, value: Any) -> None:
+        self._expirations.pop(key, None)
+        self._values[key] = value
+
+    def __delitem__(self, key):
+        self._values.pop(key, None)
+        self._expirations.pop(key, None)
+
+    def __len__(self):
+        return len(self._values)
+
+    def __iter__(self):
+        return iter(self._values)
+
+    def get(self, key: bytes, default: Any = None) -> Any:
+        return self._values.get(key, default)
+
+    def keys(self) -> Iterable[bytes]:
+        self._expire_keys()
+        return self._values.keys()
 
-    def _check_expire(self, key: bytes) -> None:
-        if key in self.expirations and self.expirations[key] < int(time.time()):
-            del self[key]
-            del self.expirations[key]
+    def values(self) -> Iterable[Any]:
+        return [v for k, v in self.items()]
 
-    def __get__(self, key: bytes) -> Any:
-        self._check_expire(key)
-        if key in self:
-            return self[key]
-        return self.__get__(key)
+    def items(self) -> Iterable[Tuple[bytes, Any]]:
+        self._expire_keys()
+        return self._values.items()
 
-    def __contains__(self, item):
-        self._check_expire(item)
-        return super().__contains__(item)
+    def update(self, values: Dict[bytes, Any]) -> None:
+        self._expire_keys()
+        self._values.update(values)
 
 
 class RedisType:
diff --git a/fakeredis/_msgs.py b/fakeredis/_msgs.py
index 1b05039f..0484edc0 100644
--- a/fakeredis/_msgs.py
+++ b/fakeredis/_msgs.py
@@ -113,6 +113,7 @@
     "TSDB: Error at upsert, update is not supported when DUPLICATE_POLICY is set to BLOCK mode"
 )
 TIMESERIES_BAD_FILTER_EXPRESSION = "TSDB: failed parsing labels"
+HEXPIRE_NUMFIELDS_DIFFERENT = "The `numfields` parameter must match the number of arguments"
 
 # Command flags
 FLAG_NO_SCRIPT = "s"  # Command not allowed in scripts
diff --git a/fakeredis/_stream.py b/fakeredis/_stream.py
index c7b1638c..953a6184 100644
--- a/fakeredis/_stream.py
+++ b/fakeredis/_stream.py
@@ -352,7 +352,7 @@ def delete(self, lst: List[Union[str, bytes]]) -> int:
                 res += 1
         return res
 
-    def add(self, fields: Sequence[bytes], entry_key: str = "*") -> Union[None, bytes]:
+    def add(self, fields: Sequence[Union[bytes, int]], entry_key: str = "*") -> Union[None, bytes]:
         """Add entry to a stream.
 
         If the entry_key cannot be added (because its timestamp is before the last entry, etc.),
diff --git a/fakeredis/commands.json b/fakeredis/commands.json
index b84f32a9..f6c25187 100644
--- a/fakeredis/commands.json
+++ b/fakeredis/commands.json
@@ -1,1 +1,1 @@
-{"append": ["append", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "bgsave": ["bgsave", -1, ["admin", "noscript", "no_async_loading"], 0, 0, 0, ["@admin", "@slow", "@dangerous"], [], [], []], "bitcount": ["bitcount", -2, ["readonly"], 1, 1, 1, ["@read", "@bitmap", "@slow"], [], [], []], "bitfield": ["bitfield", -2, ["write", "denyoom"], 1, 1, 1, ["@write", "@bitmap", "@slow"], [], [], [["bitfield_ro", -2, ["readonly", "fast"], 1, 1, 1, ["@read", "@bitmap", "@fast"], [], [], []]]], "bitop": ["bitop", -4, ["write", "denyoom"], 2, 3, 1, ["@write", "@bitmap", "@slow"], [], [], []], "bitpos": ["bitpos", -3, ["readonly"], 1, 1, 1, ["@read", "@bitmap", "@slow"], [], [], []], "blmove": ["blmove", 6, ["write", "denyoom", "blocking"], 1, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "blmpop": ["blmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "blpop": ["blpop", -3, ["write", "blocking"], 1, 1, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "brpop": ["brpop", -3, ["write", "blocking"], 1, 1, 1, ["@write", "@list", "@slow", "@blocking"], [], [], [["brpoplpush", 4, ["write", "denyoom", "blocking"], 1, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []]]], "brpoplpush": ["brpoplpush", 4, ["write", "denyoom", "blocking"], 1, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "bzmpop": ["bzmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@write", "@sortedset", "@slow", "@blocking"], [], [], []], "bzpopmax": ["bzpopmax", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast", "@blocking"], [], [], []], "bzpopmin": ["bzpopmin", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast", "@blocking"], [], [], []], "command": ["command", -1, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], [["command|count", 2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|docs", -2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|getkeys", -3, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], [["command|getkeysandflags", -3, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []]]], ["command|getkeysandflags", -3, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|info", -2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|list", -2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []]]], "dbsize": ["dbsize", 1, ["readonly", "fast"], 0, 0, 0, ["@keyspace", "@read", "@fast"], [], [], []], "decr": ["decr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], [["decrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], "decrby": ["decrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "del": ["del", -2, ["write"], 1, 1, 1, ["@keyspace", "@write", "@slow"], [], [], []], "discard": ["discard", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "dump": ["dump", 2, ["readonly"], 1, 1, 1, ["@keyspace", "@read", "@slow"], [], [], []], "echo": ["echo", 2, ["loading", "stale", "fast"], 0, 0, 0, ["@fast", "@connection"], [], [], []], "eval": ["eval", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], [["evalsha", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], [["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []]]], ["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []], ["eval_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []]]], "evalsha": ["evalsha", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], [["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []]]], "exec": ["exec", 1, ["noscript", "loading", "stale", "skip_slowlog"], 0, 0, 0, ["@slow", "@transaction"], [], [], []], "exists": ["exists", -2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "expire": ["expire", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], [["expireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], ["expiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []]]], "expireat": ["expireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "flushall": ["flushall", -1, ["write"], 0, 0, 0, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], []], "flushdb": ["flushdb", -1, ["write"], 0, 0, 0, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], []], "geoadd": ["geoadd", -5, ["write", "denyoom"], 1, 1, 1, ["@write", "@geo", "@slow"], [], [], []], "geodist": ["geodist", -4, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geohash": ["geohash", -2, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geopos": ["geopos", -2, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "georadius": ["georadius", -6, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@geo", "@slow"], [], [], [["georadiusbymember", -5, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@geo", "@slow"], [], [], [["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []]]], ["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], ["georadius_ro", -6, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []]]], "georadiusbymember": ["georadiusbymember", -5, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@geo", "@slow"], [], [], [["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []]]], "georadiusbymember_ro": ["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "georadius_ro": ["georadius_ro", -6, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geosearch": ["geosearch", -7, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], [["geosearchstore", -8, ["write", "denyoom"], 1, 2, 1, ["@write", "@geo", "@slow"], [], [], []]]], "geosearchstore": ["geosearchstore", -8, ["write", "denyoom"], 1, 2, 1, ["@write", "@geo", "@slow"], [], [], []], "get": ["get", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@string", "@fast"], [], [], [["getbit", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@bitmap", "@fast"], [], [], []], ["getdel", 2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], ["getex", -2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], ["getrange", 4, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], ["getset", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], "getbit": ["getbit", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@bitmap", "@fast"], [], [], []], "getdel": ["getdel", 2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "getex": ["getex", -2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "getrange": ["getrange", 4, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], "getset": ["getset", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "hdel": ["hdel", -3, ["write", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hexists": ["hexists", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hget": ["hget", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], [["hgetall", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []]]], "hgetall": ["hgetall", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hincrby": ["hincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], [["hincrbyfloat", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []]]], "hincrbyfloat": ["hincrbyfloat", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hkeys": ["hkeys", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hlen": ["hlen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hmget": ["hmget", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hmset": ["hmset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hrandfield": ["hrandfield", -2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hscan": ["hscan", -3, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hset": ["hset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], [["hsetnx", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []]]], "hsetnx": ["hsetnx", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hstrlen": ["hstrlen", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hvals": ["hvals", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "incr": ["incr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], [["incrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], [["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], ["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], "incrby": ["incrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], [["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], "incrbyfloat": ["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "keys": ["keys", 2, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow", "@dangerous"], [], [], []], "lastsave": ["lastsave", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@admin", "@fast", "@dangerous"], [], [], []], "lcs": ["lcs", -3, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], "lindex": ["lindex", 3, ["readonly"], 1, 1, 1, ["@read", "@list", "@slow"], [], [], []], "linsert": ["linsert", 5, ["write", "denyoom"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "llen": ["llen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@list", "@fast"], [], [], []], "lmove": ["lmove", 5, ["write", "denyoom"], 1, 2, 1, ["@write", "@list", "@slow"], [], [], []], "lmpop": ["lmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "lpop": ["lpop", -2, ["write", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "lpos": ["lpos", -3, ["readonly"], 1, 1, 1, ["@read", "@list", "@slow"], [], [], []], "lpush": ["lpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], [["lpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []]]], "lpushx": ["lpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "lrange": ["lrange", 4, ["readonly"], 1, 1, 1, ["@read", "@list", "@slow"], [], [], []], "lrem": ["lrem", 4, ["write"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "lset": ["lset", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "ltrim": ["ltrim", 4, ["write"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "mget": ["mget", -2, ["readonly", "fast"], 1, 1, 1, ["@read", "@string", "@fast"], [], [], []], "move": ["move", 3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "mset": ["mset", -3, ["write", "denyoom"], 1, 1, 2, ["@write", "@string", "@slow"], [], [], [["msetnx", -3, ["write", "denyoom"], 1, 1, 2, ["@write", "@string", "@slow"], [], [], []]]], "msetnx": ["msetnx", -3, ["write", "denyoom"], 1, 1, 2, ["@write", "@string", "@slow"], [], [], []], "multi": ["multi", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "persist": ["persist", 2, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "pexpire": ["pexpire", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], [["pexpireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], ["pexpiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []]]], "pexpireat": ["pexpireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "pfadd": ["pfadd", -2, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hyperloglog", "@fast"], [], [], []], "pfcount": ["pfcount", -2, ["readonly"], 1, 1, 1, ["@read", "@hyperloglog", "@slow"], [], [], []], "pfmerge": ["pfmerge", -2, ["write", "denyoom"], 1, 2, 1, ["@write", "@hyperloglog", "@slow"], [], [], []], "ping": ["ping", -1, ["fast"], 0, 0, 0, ["@fast", "@connection"], [], [], []], "psetex": ["psetex", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "psubscribe": ["psubscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pttl": ["pttl", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "publish": ["publish", 3, ["pubsub", "loading", "stale", "fast"], 0, 0, 0, ["@pubsub", "@fast"], [], [], []], "pubsub": ["pubsub", -2, [], 0, 0, 0, ["@slow"], [], [], [["pubsub|channels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["pubsub|numpat", 2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|numsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardchannels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardnumsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []]]], "punsubscribe": ["punsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "randomkey": ["randomkey", 1, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow"], [], [], []], "rename": ["rename", 3, ["write"], 1, 2, 1, ["@keyspace", "@write", "@slow"], [], [], [["renamenx", 3, ["write", "fast"], 1, 2, 1, ["@keyspace", "@write", "@fast"], [], [], []]]], "renamenx": ["renamenx", 3, ["write", "fast"], 1, 2, 1, ["@keyspace", "@write", "@fast"], [], [], []], "restore": ["restore", -4, ["write", "denyoom"], 1, 1, 1, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], [["restore-asking", -4, ["write", "denyoom", "asking"], 1, 1, 1, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], []]]], "rpop": ["rpop", -2, ["write", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], [["rpoplpush", 3, ["write", "denyoom"], 1, 2, 1, ["@write", "@list", "@slow"], [], [], []]]], "rpoplpush": ["rpoplpush", 3, ["write", "denyoom"], 1, 2, 1, ["@write", "@list", "@slow"], [], [], []], "rpush": ["rpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], [["rpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []]]], "rpushx": ["rpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "sadd": ["sadd", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@set", "@fast"], [], [], []], "save": ["save", 1, ["admin", "noscript", "no_async_loading", "no_multi"], 0, 0, 0, ["@admin", "@slow", "@dangerous"], [], [], []], "scan": ["scan", -2, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow"], [], [], []], "scard": ["scard", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@set", "@fast"], [], [], []], "script": ["script", -2, [], 0, 0, 0, ["@slow"], [], [], [["script|debug", 3, ["noscript"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|exists", -3, ["noscript"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|flush", -2, ["noscript"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|kill", 2, ["noscript", "allow_busy"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|load", 3, ["noscript", "stale"], 0, 0, 0, ["@slow", "@scripting"], [], [], []]]], "sdiff": ["sdiff", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sdiffstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []]]], "sdiffstore": ["sdiffstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []], "select": ["select", 2, ["loading", "stale", "fast"], 0, 0, 0, ["@fast", "@connection"], [], [], []], "set": ["set", -3, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], [["setbit", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@bitmap", "@slow"], [], [], []], ["setex", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], ["setnx", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], ["setrange", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []]]], "setbit": ["setbit", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@bitmap", "@slow"], [], [], []], "setex": ["setex", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "setnx": ["setnx", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "setrange": ["setrange", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "sinter": ["sinter", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], ["sinterstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []]]], "sintercard": ["sintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "sinterstore": ["sinterstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []], "sismember": ["sismember", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@set", "@fast"], [], [], []], "smembers": ["smembers", 2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "smismember": ["smismember", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@set", "@fast"], [], [], []], "smove": ["smove", 4, ["write", "fast"], 1, 2, 1, ["@write", "@set", "@fast"], [], [], []], "sort": ["sort", -2, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@set", "@sortedset", "@list", "@slow", "@dangerous"], [], [], [["sort_ro", -2, ["readonly", "movablekeys"], 1, 0, 1, ["@read", "@set", "@sortedset", "@list", "@slow", "@dangerous"], [], [], []]]], "spop": ["spop", -2, ["write", "fast"], 1, 1, 1, ["@write", "@set", "@fast"], [], [], []], "spublish": ["spublish", 3, ["pubsub", "loading", "stale", "fast"], 1, 1, 1, ["@pubsub", "@fast"], [], [], []], "srandmember": ["srandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "srem": ["srem", -3, ["write", "fast"], 1, 1, 1, ["@write", "@set", "@fast"], [], [], []], "sscan": ["sscan", -3, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "ssubscribe": ["ssubscribe", -2, ["pubsub", "noscript", "loading", "stale"], 1, 1, 1, ["@pubsub", "@slow"], [], [], []], "strlen": ["strlen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@string", "@fast"], [], [], []], "subscribe": ["subscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "substr": ["substr", 4, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], "sunion": ["sunion", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sunionstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []]]], "sunionstore": ["sunionstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []], "sunsubscribe": ["sunsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 1, 1, 1, ["@pubsub", "@slow"], [], [], []], "swapdb": ["swapdb", 3, ["write", "fast"], 0, 0, 0, ["@keyspace", "@write", "@fast", "@dangerous"], [], [], []], "time": ["time", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@fast"], [], [], []], "ttl": ["ttl", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "type": ["type", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "unlink": ["unlink", -2, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "unsubscribe": ["unsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "unwatch": ["unwatch", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "watch": ["watch", -2, ["noscript", "loading", "stale", "fast", "allow_busy"], 1, 1, 1, ["@fast", "@transaction"], [], [], []], "xack": ["xack", -4, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xadd": ["xadd", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xautoclaim": ["xautoclaim", -6, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xclaim": ["xclaim", -6, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xdel": ["xdel", -3, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xlen": ["xlen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@stream", "@fast"], [], [], []], "xpending": ["xpending", -3, ["readonly"], 1, 1, 1, ["@read", "@stream", "@slow"], [], [], []], "xrange": ["xrange", -4, ["readonly"], 1, 1, 1, ["@read", "@stream", "@slow"], [], [], []], "xread": ["xread", -4, ["readonly", "blocking", "movablekeys"], 0, 0, 1, ["@read", "@stream", "@slow", "@blocking"], [], [], [["xreadgroup", -7, ["write", "blocking", "movablekeys"], 0, 0, 1, ["@write", "@stream", "@slow", "@blocking"], [], [], []]]], "xreadgroup": ["xreadgroup", -7, ["write", "blocking", "movablekeys"], 0, 0, 1, ["@write", "@stream", "@slow", "@blocking"], [], [], []], "xrevrange": ["xrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@stream", "@slow"], [], [], []], "xtrim": ["xtrim", -4, ["write"], 1, 1, 1, ["@write", "@stream", "@slow"], [], [], []], "zadd": ["zadd", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zcard": ["zcard", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zcount": ["zcount", 4, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zdiff": ["zdiff", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zdiffstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zdiffstore": ["zdiffstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zincrby": ["zincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zinter": ["zinter", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], ["zinterstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zintercard": ["zintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zinterstore": ["zinterstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zlexcount": ["zlexcount", 4, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zmpop": ["zmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zmscore": ["zmscore", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zpopmax": ["zpopmax", -2, ["write", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zpopmin": ["zpopmin", -2, ["write", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zrandmember": ["zrandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrange": ["zrange", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], ["zrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], ["zrangestore", -5, ["write", "denyoom"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zrangebylex": ["zrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrangebyscore": ["zrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrangestore": ["zrangestore", -5, ["write", "denyoom"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zrank": ["zrank", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zrem": ["zrem", -3, ["write", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], [["zremrangebylex", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], ["zremrangebyrank", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], ["zremrangebyscore", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zremrangebylex": ["zremrangebylex", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zremrangebyrank": ["zremrangebyrank", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zremrangebyscore": ["zremrangebyscore", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zrevrange": ["zrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zrevrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], ["zrevrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []]]], "zrevrangebylex": ["zrevrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrevrangebyscore": ["zrevrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrevrank": ["zrevrank", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zscan": ["zscan", -3, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zscore": ["zscore", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zunion": ["zunion", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zunionstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zunionstore": ["zunionstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "json.del": ["json.del", -1, [], 0, 0, 0, [], [], [], []], "json.forget": ["json.forget", -1, [], 0, 0, 0, [], [], [], []], "json.get": ["json.get", -1, [], 0, 0, 0, [], [], [], []], "json.toggle": ["json.toggle", -1, [], 0, 0, 0, [], [], [], []], "json.clear": ["json.clear", -1, [], 0, 0, 0, [], [], [], []], "json.set": ["json.set", -1, [], 0, 0, 0, [], [], [], []], "json.mset": ["json.mset", -1, [], 0, 0, 0, [], [], [], []], "json.merge": ["json.merge", -1, [], 0, 0, 0, [], [], [], []], "json.mget": ["json.mget", -1, [], 0, 0, 0, [], [], [], []], "json.numincrby": ["json.numincrby", -1, [], 0, 0, 0, [], [], [], []], "json.nummultby": ["json.nummultby", -1, [], 0, 0, 0, [], [], [], []], "json.strappend": ["json.strappend", -1, [], 0, 0, 0, [], [], [], []], "json.strlen": ["json.strlen", -1, [], 0, 0, 0, [], [], [], []], "json.arrappend": ["json.arrappend", -1, [], 0, 0, 0, [], [], [], []], "json.arrindex": ["json.arrindex", -1, [], 0, 0, 0, [], [], [], []], "json.arrinsert": ["json.arrinsert", -1, [], 0, 0, 0, [], [], [], []], "json.arrlen": ["json.arrlen", -1, [], 0, 0, 0, [], [], [], []], "json.arrpop": ["json.arrpop", -1, [], 0, 0, 0, [], [], [], []], "json.arrtrim": ["json.arrtrim", -1, [], 0, 0, 0, [], [], [], []], "json.objkeys": ["json.objkeys", -1, [], 0, 0, 0, [], [], [], []], "json.objlen": ["json.objlen", -1, [], 0, 0, 0, [], [], [], []], "json.type": ["json.type", -1, [], 0, 0, 0, [], [], [], []], "ts.create": ["ts.create", -1, [], 0, 0, 0, [], [], [], [["ts.createrule", -1, [], 0, 0, 0, [], [], [], []]]], "ts.del": ["ts.del", -1, [], 0, 0, 0, [], [], [], [["ts.deleterule", -1, [], 0, 0, 0, [], [], [], []]]], "ts.alter": ["ts.alter", -1, [], 0, 0, 0, [], [], [], []], "ts.add": ["ts.add", -1, [], 0, 0, 0, [], [], [], []], "ts.madd": ["ts.madd", -1, [], 0, 0, 0, [], [], [], []], "ts.incrby": ["ts.incrby", -1, [], 0, 0, 0, [], [], [], []], "ts.decrby": ["ts.decrby", -1, [], 0, 0, 0, [], [], [], []], "ts.createrule": ["ts.createrule", -1, [], 0, 0, 0, [], [], [], []], "ts.deleterule": ["ts.deleterule", -1, [], 0, 0, 0, [], [], [], []], "ts.range": ["ts.range", -1, [], 0, 0, 0, [], [], [], []], "ts.revrange": ["ts.revrange", -1, [], 0, 0, 0, [], [], [], []], "ts.mrange": ["ts.mrange", -1, [], 0, 0, 0, [], [], [], []], "ts.mrevrange": ["ts.mrevrange", -1, [], 0, 0, 0, [], [], [], []], "ts.get": ["ts.get", -1, [], 0, 0, 0, [], [], [], []], "ts.mget": ["ts.mget", -1, [], 0, 0, 0, [], [], [], []], "ts.info": ["ts.info", -1, [], 0, 0, 0, [], [], [], []], "ts.queryindex": ["ts.queryindex", -1, [], 0, 0, 0, [], [], [], []], "bf.reserve": ["bf.reserve", -1, [], 0, 0, 0, [], [], [], []], "bf.add": ["bf.add", -1, [], 0, 0, 0, [], [], [], []], "bf.madd": ["bf.madd", -1, [], 0, 0, 0, [], [], [], []], "bf.insert": ["bf.insert", -1, [], 0, 0, 0, [], [], [], []], "bf.exists": ["bf.exists", -1, [], 0, 0, 0, [], [], [], []], "bf.mexists": ["bf.mexists", -1, [], 0, 0, 0, [], [], [], []], "bf.scandump": ["bf.scandump", -1, [], 0, 0, 0, [], [], [], []], "bf.loadchunk": ["bf.loadchunk", -1, [], 0, 0, 0, [], [], [], []], "bf.info": ["bf.info", -1, [], 0, 0, 0, [], [], [], []], "bf.card": ["bf.card", -1, [], 0, 0, 0, [], [], [], []], "cf.reserve": ["cf.reserve", -1, [], 0, 0, 0, [], [], [], []], "cf.add": ["cf.add", -1, [], 0, 0, 0, [], [], [], [["cf.addnx", -1, [], 0, 0, 0, [], [], [], []]]], "cf.addnx": ["cf.addnx", -1, [], 0, 0, 0, [], [], [], []], "cf.insert": ["cf.insert", -1, [], 0, 0, 0, [], [], [], [["cf.insertnx", -1, [], 0, 0, 0, [], [], [], []]]], "cf.insertnx": ["cf.insertnx", -1, [], 0, 0, 0, [], [], [], []], "cf.exists": ["cf.exists", -1, [], 0, 0, 0, [], [], [], []], "cf.mexists": ["cf.mexists", -1, [], 0, 0, 0, [], [], [], []], "cf.del": ["cf.del", -1, [], 0, 0, 0, [], [], [], []], "cf.count": ["cf.count", -1, [], 0, 0, 0, [], [], [], []], "cf.scandump": ["cf.scandump", -1, [], 0, 0, 0, [], [], [], []], "cf.loadchunk": ["cf.loadchunk", -1, [], 0, 0, 0, [], [], [], []], "cf.info": ["cf.info", -1, [], 0, 0, 0, [], [], [], []], "cms.initbydim": ["cms.initbydim", -1, [], 0, 0, 0, [], [], [], []], "cms.initbyprob": ["cms.initbyprob", -1, [], 0, 0, 0, [], [], [], []], "cms.incrby": ["cms.incrby", -1, [], 0, 0, 0, [], [], [], []], "cms.query": ["cms.query", -1, [], 0, 0, 0, [], [], [], []], "cms.merge": ["cms.merge", -1, [], 0, 0, 0, [], [], [], []], "cms.info": ["cms.info", -1, [], 0, 0, 0, [], [], [], []], "topk.reserve": ["topk.reserve", -1, [], 0, 0, 0, [], [], [], []], "topk.add": ["topk.add", -1, [], 0, 0, 0, [], [], [], []], "topk.incrby": ["topk.incrby", -1, [], 0, 0, 0, [], [], [], []], "topk.query": ["topk.query", -1, [], 0, 0, 0, [], [], [], []], "topk.count": ["topk.count", -1, [], 0, 0, 0, [], [], [], []], "topk.list": ["topk.list", -1, [], 0, 0, 0, [], [], [], []], "topk.info": ["topk.info", -1, [], 0, 0, 0, [], [], [], []], "tdigest.create": ["tdigest.create", -1, [], 0, 0, 0, [], [], [], []], "tdigest.reset": ["tdigest.reset", -1, [], 0, 0, 0, [], [], [], []], "tdigest.add": ["tdigest.add", -1, [], 0, 0, 0, [], [], [], []], "tdigest.merge": ["tdigest.merge", -1, [], 0, 0, 0, [], [], [], []], "tdigest.min": ["tdigest.min", -1, [], 0, 0, 0, [], [], [], []], "tdigest.max": ["tdigest.max", -1, [], 0, 0, 0, [], [], [], []], "tdigest.quantile": ["tdigest.quantile", -1, [], 0, 0, 0, [], [], [], []], "tdigest.cdf": ["tdigest.cdf", -1, [], 0, 0, 0, [], [], [], []], "tdigest.trimmed_mean": ["tdigest.trimmed_mean", -1, [], 0, 0, 0, [], [], [], []], "tdigest.rank": ["tdigest.rank", -1, [], 0, 0, 0, [], [], [], []], "tdigest.revrank": ["tdigest.revrank", -1, [], 0, 0, 0, [], [], [], []], "tdigest.byrank": ["tdigest.byrank", -1, [], 0, 0, 0, [], [], [], []], "tdigest.byrevrank": ["tdigest.byrevrank", -1, [], 0, 0, 0, [], [], [], []], "tdigest.info": ["tdigest.info", -1, [], 0, 0, 0, [], [], [], []]}
\ No newline at end of file
+{"append": ["append", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "bgsave": ["bgsave", -1, ["admin", "noscript", "no_async_loading"], 0, 0, 0, ["@admin", "@slow", "@dangerous"], [], [], []], "bitcount": ["bitcount", -2, ["readonly"], 1, 1, 1, ["@read", "@bitmap", "@slow"], [], [], []], "bitfield": ["bitfield", -2, ["write", "denyoom"], 1, 1, 1, ["@write", "@bitmap", "@slow"], [], [], [["bitfield_ro", -2, ["readonly", "fast"], 1, 1, 1, ["@read", "@bitmap", "@fast"], [], [], []]]], "bitop": ["bitop", -4, ["write", "denyoom"], 2, 3, 1, ["@write", "@bitmap", "@slow"], [], [], []], "bitpos": ["bitpos", -3, ["readonly"], 1, 1, 1, ["@read", "@bitmap", "@slow"], [], [], []], "blmove": ["blmove", 6, ["write", "denyoom", "blocking"], 1, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "blmpop": ["blmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "blpop": ["blpop", -3, ["write", "blocking"], 1, 1, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "brpop": ["brpop", -3, ["write", "blocking"], 1, 1, 1, ["@write", "@list", "@slow", "@blocking"], [], [], [["brpoplpush", 4, ["write", "denyoom", "blocking"], 1, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []]]], "brpoplpush": ["brpoplpush", 4, ["write", "denyoom", "blocking"], 1, 2, 1, ["@write", "@list", "@slow", "@blocking"], [], [], []], "bzmpop": ["bzmpop", -5, ["write", "blocking", "movablekeys"], 2, 2, 1, ["@write", "@sortedset", "@slow", "@blocking"], [], [], []], "bzpopmax": ["bzpopmax", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast", "@blocking"], [], [], []], "bzpopmin": ["bzpopmin", -3, ["write", "blocking", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast", "@blocking"], [], [], []], "command": ["command", -1, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], [["command|count", 2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|docs", -2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|getkeys", -3, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], [["command|getkeysandflags", -3, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []]]], ["command|getkeysandflags", -3, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|info", -2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []], ["command|list", -2, ["loading", "stale"], 0, 0, 0, ["@slow", "@connection"], [], [], []]]], "dbsize": ["dbsize", 1, ["readonly", "fast"], 0, 0, 0, ["@keyspace", "@read", "@fast"], [], [], []], "decr": ["decr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], [["decrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], "decrby": ["decrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "del": ["del", -2, ["write"], 1, 1, 1, ["@keyspace", "@write", "@slow"], [], [], []], "discard": ["discard", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "dump": ["dump", 2, ["readonly"], 1, 1, 1, ["@keyspace", "@read", "@slow"], [], [], []], "echo": ["echo", 2, ["loading", "stale", "fast"], 0, 0, 0, ["@fast", "@connection"], [], [], []], "eval": ["eval", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], [["evalsha", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], [["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []]]], ["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []], ["eval_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []]]], "evalsha": ["evalsha", -3, ["noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], [["evalsha_ro", -3, ["readonly", "noscript", "stale", "skip_monitor", "no_mandatory_keys", "movablekeys"], 2, 2, 1, ["@slow", "@scripting"], [], [], []]]], "exec": ["exec", 1, ["noscript", "loading", "stale", "skip_slowlog"], 0, 0, 0, ["@slow", "@transaction"], [], [], []], "exists": ["exists", -2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "expire": ["expire", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], [["expireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], ["expiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []]]], "expireat": ["expireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "flushall": ["flushall", -1, ["write"], 0, 0, 0, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], []], "flushdb": ["flushdb", -1, ["write"], 0, 0, 0, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], []], "geoadd": ["geoadd", -5, ["write", "denyoom"], 1, 1, 1, ["@write", "@geo", "@slow"], [], [], []], "geodist": ["geodist", -4, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geohash": ["geohash", -2, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geopos": ["geopos", -2, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "georadius": ["georadius", -6, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@geo", "@slow"], [], [], [["georadiusbymember", -5, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@geo", "@slow"], [], [], [["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []]]], ["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], ["georadius_ro", -6, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []]]], "georadiusbymember": ["georadiusbymember", -5, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@geo", "@slow"], [], [], [["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []]]], "georadiusbymember_ro": ["georadiusbymember_ro", -5, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "georadius_ro": ["georadius_ro", -6, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], []], "geosearch": ["geosearch", -7, ["readonly"], 1, 1, 1, ["@read", "@geo", "@slow"], [], [], [["geosearchstore", -8, ["write", "denyoom"], 1, 2, 1, ["@write", "@geo", "@slow"], [], [], []]]], "geosearchstore": ["geosearchstore", -8, ["write", "denyoom"], 1, 2, 1, ["@write", "@geo", "@slow"], [], [], []], "get": ["get", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@string", "@fast"], [], [], [["getbit", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@bitmap", "@fast"], [], [], []], ["getdel", 2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], ["getex", -2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], ["getrange", 4, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], ["getset", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], "getbit": ["getbit", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@bitmap", "@fast"], [], [], []], "getdel": ["getdel", 2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "getex": ["getex", -2, ["write", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "getrange": ["getrange", 4, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], "getset": ["getset", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "hdel": ["hdel", -3, ["write", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hexists": ["hexists", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hexpire": ["hexpire", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], [["hexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], ["hexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []]]], "hexpireat": ["hexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hexpiretime": ["hexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hget": ["hget", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], [["hgetall", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], ["hgetf", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []]]], "hgetall": ["hgetall", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hincrby": ["hincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], [["hincrbyfloat", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []]]], "hincrbyfloat": ["hincrbyfloat", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hkeys": ["hkeys", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hlen": ["hlen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hmget": ["hmget", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hmset": ["hmset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hpersist": ["hpersist", -4, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hpexpire": ["hpexpire", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], [["hpexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], ["hpexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []]]], "hpexpireat": ["hpexpireat", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hpexpiretime": ["hpexpiretime", -4, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hpttl": ["hpttl", -4, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hrandfield": ["hrandfield", -2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hscan": ["hscan", -3, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "hset": ["hset", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], [["hsetf", -6, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], ["hsetnx", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []]]], "hsetnx": ["hsetnx", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hash", "@fast"], [], [], []], "hstrlen": ["hstrlen", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "httl": ["httl", -4, ["readonly", "fast"], 1, 1, 1, ["@read", "@hash", "@fast"], [], [], []], "hvals": ["hvals", 2, ["readonly"], 1, 1, 1, ["@read", "@hash", "@slow"], [], [], []], "incr": ["incr", 2, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], [["incrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], [["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], ["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], "incrby": ["incrby", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], [["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []]]], "incrbyfloat": ["incrbyfloat", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "keys": ["keys", 2, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow", "@dangerous"], [], [], []], "lastsave": ["lastsave", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@admin", "@fast", "@dangerous"], [], [], []], "lcs": ["lcs", -3, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], "lindex": ["lindex", 3, ["readonly"], 1, 1, 1, ["@read", "@list", "@slow"], [], [], []], "linsert": ["linsert", 5, ["write", "denyoom"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "llen": ["llen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@list", "@fast"], [], [], []], "lmove": ["lmove", 5, ["write", "denyoom"], 1, 2, 1, ["@write", "@list", "@slow"], [], [], []], "lmpop": ["lmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "lpop": ["lpop", -2, ["write", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "lpos": ["lpos", -3, ["readonly"], 1, 1, 1, ["@read", "@list", "@slow"], [], [], []], "lpush": ["lpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], [["lpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []]]], "lpushx": ["lpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "lrange": ["lrange", 4, ["readonly"], 1, 1, 1, ["@read", "@list", "@slow"], [], [], []], "lrem": ["lrem", 4, ["write"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "lset": ["lset", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "ltrim": ["ltrim", 4, ["write"], 1, 1, 1, ["@write", "@list", "@slow"], [], [], []], "mget": ["mget", -2, ["readonly", "fast"], 1, 1, 1, ["@read", "@string", "@fast"], [], [], []], "move": ["move", 3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "mset": ["mset", -3, ["write", "denyoom"], 1, 1, 2, ["@write", "@string", "@slow"], [], [], [["msetnx", -3, ["write", "denyoom"], 1, 1, 2, ["@write", "@string", "@slow"], [], [], []]]], "msetnx": ["msetnx", -3, ["write", "denyoom"], 1, 1, 2, ["@write", "@string", "@slow"], [], [], []], "multi": ["multi", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "persist": ["persist", 2, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "pexpire": ["pexpire", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], [["pexpireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], ["pexpiretime", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []]]], "pexpireat": ["pexpireat", -3, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "pfadd": ["pfadd", -2, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@hyperloglog", "@fast"], [], [], []], "pfcount": ["pfcount", -2, ["readonly"], 1, 1, 1, ["@read", "@hyperloglog", "@slow"], [], [], []], "pfmerge": ["pfmerge", -2, ["write", "denyoom"], 1, 2, 1, ["@write", "@hyperloglog", "@slow"], [], [], []], "ping": ["ping", -1, ["fast"], 0, 0, 0, ["@fast", "@connection"], [], [], []], "psetex": ["psetex", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "psubscribe": ["psubscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "pttl": ["pttl", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "publish": ["publish", 3, ["pubsub", "loading", "stale", "fast"], 0, 0, 0, ["@pubsub", "@fast"], [], [], []], "pubsub": ["pubsub", -2, [], 0, 0, 0, ["@slow"], [], [], [["pubsub|channels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow"], [], [], []], ["pubsub|numpat", 2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|numsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardchannels", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], ["pubsub|shardnumsub", -2, ["pubsub", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []]]], "punsubscribe": ["punsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "randomkey": ["randomkey", 1, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow"], [], [], []], "rename": ["rename", 3, ["write"], 1, 2, 1, ["@keyspace", "@write", "@slow"], [], [], [["renamenx", 3, ["write", "fast"], 1, 2, 1, ["@keyspace", "@write", "@fast"], [], [], []]]], "renamenx": ["renamenx", 3, ["write", "fast"], 1, 2, 1, ["@keyspace", "@write", "@fast"], [], [], []], "restore": ["restore", -4, ["write", "denyoom"], 1, 1, 1, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], [["restore-asking", -4, ["write", "denyoom", "asking"], 1, 1, 1, ["@keyspace", "@write", "@slow", "@dangerous"], [], [], []]]], "rpop": ["rpop", -2, ["write", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], [["rpoplpush", 3, ["write", "denyoom"], 1, 2, 1, ["@write", "@list", "@slow"], [], [], []]]], "rpoplpush": ["rpoplpush", 3, ["write", "denyoom"], 1, 2, 1, ["@write", "@list", "@slow"], [], [], []], "rpush": ["rpush", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], [["rpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []]]], "rpushx": ["rpushx", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@list", "@fast"], [], [], []], "sadd": ["sadd", -3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@set", "@fast"], [], [], []], "save": ["save", 1, ["admin", "noscript", "no_async_loading", "no_multi"], 0, 0, 0, ["@admin", "@slow", "@dangerous"], [], [], []], "scan": ["scan", -2, ["readonly"], 0, 0, 0, ["@keyspace", "@read", "@slow"], [], [], []], "scard": ["scard", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@set", "@fast"], [], [], []], "script": ["script", -2, [], 0, 0, 0, ["@slow"], [], [], [["script|debug", 3, ["noscript"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|exists", -3, ["noscript"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|flush", -2, ["noscript"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|help", 2, ["loading", "stale"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|kill", 2, ["noscript", "allow_busy"], 0, 0, 0, ["@slow", "@scripting"], [], [], []], ["script|load", 3, ["noscript", "stale"], 0, 0, 0, ["@slow", "@scripting"], [], [], []]]], "sdiff": ["sdiff", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sdiffstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []]]], "sdiffstore": ["sdiffstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []], "select": ["select", 2, ["loading", "stale", "fast"], 0, 0, 0, ["@fast", "@connection"], [], [], []], "set": ["set", -3, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], [["setbit", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@bitmap", "@slow"], [], [], []], ["setex", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], ["setnx", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], ["setrange", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []]]], "setbit": ["setbit", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@bitmap", "@slow"], [], [], []], "setex": ["setex", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "setnx": ["setnx", 3, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@string", "@fast"], [], [], []], "setrange": ["setrange", 4, ["write", "denyoom"], 1, 1, 1, ["@write", "@string", "@slow"], [], [], []], "sinter": ["sinter", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], ["sinterstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []]]], "sintercard": ["sintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "sinterstore": ["sinterstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []], "sismember": ["sismember", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@set", "@fast"], [], [], []], "smembers": ["smembers", 2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "smismember": ["smismember", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@set", "@fast"], [], [], []], "smove": ["smove", 4, ["write", "fast"], 1, 2, 1, ["@write", "@set", "@fast"], [], [], []], "sort": ["sort", -2, ["write", "denyoom", "movablekeys"], 1, 0, 1, ["@write", "@set", "@sortedset", "@list", "@slow", "@dangerous"], [], [], [["sort_ro", -2, ["readonly", "movablekeys"], 1, 0, 1, ["@read", "@set", "@sortedset", "@list", "@slow", "@dangerous"], [], [], []]]], "spop": ["spop", -2, ["write", "fast"], 1, 1, 1, ["@write", "@set", "@fast"], [], [], []], "spublish": ["spublish", 3, ["pubsub", "loading", "stale", "fast"], 1, 1, 1, ["@pubsub", "@fast"], [], [], []], "srandmember": ["srandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "srem": ["srem", -3, ["write", "fast"], 1, 1, 1, ["@write", "@set", "@fast"], [], [], []], "sscan": ["sscan", -3, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], []], "ssubscribe": ["ssubscribe", -2, ["pubsub", "noscript", "loading", "stale"], 1, 1, 1, ["@pubsub", "@slow"], [], [], []], "strlen": ["strlen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@string", "@fast"], [], [], []], "subscribe": ["subscribe", -2, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "substr": ["substr", 4, ["readonly"], 1, 1, 1, ["@read", "@string", "@slow"], [], [], []], "sunion": ["sunion", -2, ["readonly"], 1, 1, 1, ["@read", "@set", "@slow"], [], [], [["sunionstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []]]], "sunionstore": ["sunionstore", -3, ["write", "denyoom"], 1, 2, 1, ["@write", "@set", "@slow"], [], [], []], "sunsubscribe": ["sunsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 1, 1, 1, ["@pubsub", "@slow"], [], [], []], "swapdb": ["swapdb", 3, ["write", "fast"], 0, 0, 0, ["@keyspace", "@write", "@fast", "@dangerous"], [], [], []], "time": ["time", 1, ["loading", "stale", "fast"], 0, 0, 0, ["@fast"], [], [], []], "ttl": ["ttl", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "type": ["type", 2, ["readonly", "fast"], 1, 1, 1, ["@keyspace", "@read", "@fast"], [], [], []], "unlink": ["unlink", -2, ["write", "fast"], 1, 1, 1, ["@keyspace", "@write", "@fast"], [], [], []], "unsubscribe": ["unsubscribe", -1, ["pubsub", "noscript", "loading", "stale"], 0, 0, 0, ["@pubsub", "@slow"], [], [], []], "unwatch": ["unwatch", 1, ["noscript", "loading", "stale", "fast", "allow_busy"], 0, 0, 0, ["@fast", "@transaction"], [], [], []], "watch": ["watch", -2, ["noscript", "loading", "stale", "fast", "allow_busy"], 1, 1, 1, ["@fast", "@transaction"], [], [], []], "xack": ["xack", -4, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xadd": ["xadd", -5, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xautoclaim": ["xautoclaim", -6, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xclaim": ["xclaim", -6, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xdel": ["xdel", -3, ["write", "fast"], 1, 1, 1, ["@write", "@stream", "@fast"], [], [], []], "xlen": ["xlen", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@stream", "@fast"], [], [], []], "xpending": ["xpending", -3, ["readonly"], 1, 1, 1, ["@read", "@stream", "@slow"], [], [], []], "xrange": ["xrange", -4, ["readonly"], 1, 1, 1, ["@read", "@stream", "@slow"], [], [], []], "xread": ["xread", -4, ["readonly", "blocking", "movablekeys"], 0, 0, 1, ["@read", "@stream", "@slow", "@blocking"], [], [], [["xreadgroup", -7, ["write", "blocking", "movablekeys"], 0, 0, 1, ["@write", "@stream", "@slow", "@blocking"], [], [], []]]], "xreadgroup": ["xreadgroup", -7, ["write", "blocking", "movablekeys"], 0, 0, 1, ["@write", "@stream", "@slow", "@blocking"], [], [], []], "xrevrange": ["xrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@stream", "@slow"], [], [], []], "xtrim": ["xtrim", -4, ["write"], 1, 1, 1, ["@write", "@stream", "@slow"], [], [], []], "zadd": ["zadd", -4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zcard": ["zcard", 2, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zcount": ["zcount", 4, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zdiff": ["zdiff", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zdiffstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zdiffstore": ["zdiffstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zincrby": ["zincrby", 4, ["write", "denyoom", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zinter": ["zinter", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], ["zinterstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zintercard": ["zintercard", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zinterstore": ["zinterstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zlexcount": ["zlexcount", 4, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zmpop": ["zmpop", -4, ["write", "movablekeys"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zmscore": ["zmscore", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zpopmax": ["zpopmax", -2, ["write", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zpopmin": ["zpopmin", -2, ["write", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], []], "zrandmember": ["zrandmember", -2, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrange": ["zrange", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], ["zrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], ["zrangestore", -5, ["write", "denyoom"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zrangebylex": ["zrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrangebyscore": ["zrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrangestore": ["zrangestore", -5, ["write", "denyoom"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zrank": ["zrank", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zrem": ["zrem", -3, ["write", "fast"], 1, 1, 1, ["@write", "@sortedset", "@fast"], [], [], [["zremrangebylex", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], ["zremrangebyrank", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], ["zremrangebyscore", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zremrangebylex": ["zremrangebylex", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zremrangebyrank": ["zremrangebyrank", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zremrangebyscore": ["zremrangebyscore", 4, ["write"], 1, 1, 1, ["@write", "@sortedset", "@slow"], [], [], []], "zrevrange": ["zrevrange", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zrevrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], ["zrevrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []]]], "zrevrangebylex": ["zrevrangebylex", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrevrangebyscore": ["zrevrangebyscore", -4, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zrevrank": ["zrevrank", -3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zscan": ["zscan", -3, ["readonly"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], []], "zscore": ["zscore", 3, ["readonly", "fast"], 1, 1, 1, ["@read", "@sortedset", "@fast"], [], [], []], "zunion": ["zunion", -3, ["readonly", "movablekeys"], 1, 1, 1, ["@read", "@sortedset", "@slow"], [], [], [["zunionstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []]]], "zunionstore": ["zunionstore", -4, ["write", "denyoom", "movablekeys"], 1, 2, 1, ["@write", "@sortedset", "@slow"], [], [], []], "json.del": ["json.del", -1, [], 0, 0, 0, [], [], [], []], "json.forget": ["json.forget", -1, [], 0, 0, 0, [], [], [], []], "json.get": ["json.get", -1, [], 0, 0, 0, [], [], [], []], "json.toggle": ["json.toggle", -1, [], 0, 0, 0, [], [], [], []], "json.clear": ["json.clear", -1, [], 0, 0, 0, [], [], [], []], "json.set": ["json.set", -1, [], 0, 0, 0, [], [], [], []], "json.mset": ["json.mset", -1, [], 0, 0, 0, [], [], [], []], "json.merge": ["json.merge", -1, [], 0, 0, 0, [], [], [], []], "json.mget": ["json.mget", -1, [], 0, 0, 0, [], [], [], []], "json.numincrby": ["json.numincrby", -1, [], 0, 0, 0, [], [], [], []], "json.nummultby": ["json.nummultby", -1, [], 0, 0, 0, [], [], [], []], "json.strappend": ["json.strappend", -1, [], 0, 0, 0, [], [], [], []], "json.strlen": ["json.strlen", -1, [], 0, 0, 0, [], [], [], []], "json.arrappend": ["json.arrappend", -1, [], 0, 0, 0, [], [], [], []], "json.arrindex": ["json.arrindex", -1, [], 0, 0, 0, [], [], [], []], "json.arrinsert": ["json.arrinsert", -1, [], 0, 0, 0, [], [], [], []], "json.arrlen": ["json.arrlen", -1, [], 0, 0, 0, [], [], [], []], "json.arrpop": ["json.arrpop", -1, [], 0, 0, 0, [], [], [], []], "json.arrtrim": ["json.arrtrim", -1, [], 0, 0, 0, [], [], [], []], "json.objkeys": ["json.objkeys", -1, [], 0, 0, 0, [], [], [], []], "json.objlen": ["json.objlen", -1, [], 0, 0, 0, [], [], [], []], "json.type": ["json.type", -1, [], 0, 0, 0, [], [], [], []], "ts.create": ["ts.create", -1, [], 0, 0, 0, [], [], [], [["ts.createrule", -1, [], 0, 0, 0, [], [], [], []]]], "ts.del": ["ts.del", -1, [], 0, 0, 0, [], [], [], [["ts.deleterule", -1, [], 0, 0, 0, [], [], [], []]]], "ts.alter": ["ts.alter", -1, [], 0, 0, 0, [], [], [], []], "ts.add": ["ts.add", -1, [], 0, 0, 0, [], [], [], []], "ts.madd": ["ts.madd", -1, [], 0, 0, 0, [], [], [], []], "ts.incrby": ["ts.incrby", -1, [], 0, 0, 0, [], [], [], []], "ts.decrby": ["ts.decrby", -1, [], 0, 0, 0, [], [], [], []], "ts.createrule": ["ts.createrule", -1, [], 0, 0, 0, [], [], [], []], "ts.deleterule": ["ts.deleterule", -1, [], 0, 0, 0, [], [], [], []], "ts.range": ["ts.range", -1, [], 0, 0, 0, [], [], [], []], "ts.revrange": ["ts.revrange", -1, [], 0, 0, 0, [], [], [], []], "ts.mrange": ["ts.mrange", -1, [], 0, 0, 0, [], [], [], []], "ts.mrevrange": ["ts.mrevrange", -1, [], 0, 0, 0, [], [], [], []], "ts.get": ["ts.get", -1, [], 0, 0, 0, [], [], [], []], "ts.mget": ["ts.mget", -1, [], 0, 0, 0, [], [], [], []], "ts.info": ["ts.info", -1, [], 0, 0, 0, [], [], [], []], "ts.queryindex": ["ts.queryindex", -1, [], 0, 0, 0, [], [], [], []], "bf.reserve": ["bf.reserve", -1, [], 0, 0, 0, [], [], [], []], "bf.add": ["bf.add", -1, [], 0, 0, 0, [], [], [], []], "bf.madd": ["bf.madd", -1, [], 0, 0, 0, [], [], [], []], "bf.insert": ["bf.insert", -1, [], 0, 0, 0, [], [], [], []], "bf.exists": ["bf.exists", -1, [], 0, 0, 0, [], [], [], []], "bf.mexists": ["bf.mexists", -1, [], 0, 0, 0, [], [], [], []], "bf.scandump": ["bf.scandump", -1, [], 0, 0, 0, [], [], [], []], "bf.loadchunk": ["bf.loadchunk", -1, [], 0, 0, 0, [], [], [], []], "bf.info": ["bf.info", -1, [], 0, 0, 0, [], [], [], []], "bf.card": ["bf.card", -1, [], 0, 0, 0, [], [], [], []], "cf.reserve": ["cf.reserve", -1, [], 0, 0, 0, [], [], [], []], "cf.add": ["cf.add", -1, [], 0, 0, 0, [], [], [], [["cf.addnx", -1, [], 0, 0, 0, [], [], [], []]]], "cf.addnx": ["cf.addnx", -1, [], 0, 0, 0, [], [], [], []], "cf.insert": ["cf.insert", -1, [], 0, 0, 0, [], [], [], [["cf.insertnx", -1, [], 0, 0, 0, [], [], [], []]]], "cf.insertnx": ["cf.insertnx", -1, [], 0, 0, 0, [], [], [], []], "cf.exists": ["cf.exists", -1, [], 0, 0, 0, [], [], [], []], "cf.mexists": ["cf.mexists", -1, [], 0, 0, 0, [], [], [], []], "cf.del": ["cf.del", -1, [], 0, 0, 0, [], [], [], []], "cf.count": ["cf.count", -1, [], 0, 0, 0, [], [], [], []], "cf.scandump": ["cf.scandump", -1, [], 0, 0, 0, [], [], [], []], "cf.loadchunk": ["cf.loadchunk", -1, [], 0, 0, 0, [], [], [], []], "cf.info": ["cf.info", -1, [], 0, 0, 0, [], [], [], []], "cms.initbydim": ["cms.initbydim", -1, [], 0, 0, 0, [], [], [], []], "cms.initbyprob": ["cms.initbyprob", -1, [], 0, 0, 0, [], [], [], []], "cms.incrby": ["cms.incrby", -1, [], 0, 0, 0, [], [], [], []], "cms.query": ["cms.query", -1, [], 0, 0, 0, [], [], [], []], "cms.merge": ["cms.merge", -1, [], 0, 0, 0, [], [], [], []], "cms.info": ["cms.info", -1, [], 0, 0, 0, [], [], [], []], "topk.reserve": ["topk.reserve", -1, [], 0, 0, 0, [], [], [], []], "topk.add": ["topk.add", -1, [], 0, 0, 0, [], [], [], []], "topk.incrby": ["topk.incrby", -1, [], 0, 0, 0, [], [], [], []], "topk.query": ["topk.query", -1, [], 0, 0, 0, [], [], [], []], "topk.count": ["topk.count", -1, [], 0, 0, 0, [], [], [], []], "topk.list": ["topk.list", -1, [], 0, 0, 0, [], [], [], []], "topk.info": ["topk.info", -1, [], 0, 0, 0, [], [], [], []], "tdigest.create": ["tdigest.create", -1, [], 0, 0, 0, [], [], [], []], "tdigest.reset": ["tdigest.reset", -1, [], 0, 0, 0, [], [], [], []], "tdigest.add": ["tdigest.add", -1, [], 0, 0, 0, [], [], [], []], "tdigest.merge": ["tdigest.merge", -1, [], 0, 0, 0, [], [], [], []], "tdigest.min": ["tdigest.min", -1, [], 0, 0, 0, [], [], [], []], "tdigest.max": ["tdigest.max", -1, [], 0, 0, 0, [], [], [], []], "tdigest.quantile": ["tdigest.quantile", -1, [], 0, 0, 0, [], [], [], []], "tdigest.cdf": ["tdigest.cdf", -1, [], 0, 0, 0, [], [], [], []], "tdigest.trimmed_mean": ["tdigest.trimmed_mean", -1, [], 0, 0, 0, [], [], [], []], "tdigest.rank": ["tdigest.rank", -1, [], 0, 0, 0, [], [], [], []], "tdigest.revrank": ["tdigest.revrank", -1, [], 0, 0, 0, [], [], [], []], "tdigest.byrank": ["tdigest.byrank", -1, [], 0, 0, 0, [], [], [], []], "tdigest.byrevrank": ["tdigest.byrevrank", -1, [], 0, 0, 0, [], [], [], []], "tdigest.info": ["tdigest.info", -1, [], 0, 0, 0, [], [], [], []]}
\ No newline at end of file
diff --git a/fakeredis/commands_mixins/generic_mixin.py b/fakeredis/commands_mixins/generic_mixin.py
index 1b33fdf9..1ebb9a6d 100644
--- a/fakeredis/commands_mixins/generic_mixin.py
+++ b/fakeredis/commands_mixins/generic_mixin.py
@@ -15,6 +15,7 @@
     SortFloat,
     delete_keys,
     Item,
+    Hash,
 )
 from fakeredis._helpers import compile_pattern, SimpleError, OK, casematch, Database, SimpleString
 from fakeredis._zset import ZSet
@@ -52,7 +53,7 @@ def _lookup_key(self, key, pattern):
         if item.value is None:
             return None
         if field is not None:
-            if not isinstance(item.value, dict):
+            if not isinstance(item.value, Hash):
                 return None
             return item.value.get(field)
         else:
diff --git a/fakeredis/commands_mixins/hash_mixin.py b/fakeredis/commands_mixins/hash_mixin.py
index a7359409..bb7019e2 100644
--- a/fakeredis/commands_mixins/hash_mixin.py
+++ b/fakeredis/commands_mixins/hash_mixin.py
@@ -4,8 +4,10 @@
 from typing import Callable, List, Tuple, Any, Optional
 
 from fakeredis import _msgs as msgs
+from fakeredis._command_args_parsing import extract_args
 from fakeredis._commands import command, Key, Hash, Int, Float, CommandItem
 from fakeredis._helpers import SimpleError, OK, casematch, SimpleString
+from fakeredis._helpers import current_time
 
 
 class HashCommandsMixin:
@@ -134,3 +136,120 @@ def hrandfield(self, key: CommandItem, *args: bytes) -> Optional[List[bytes]]:
         else:
             res = [t[0] for t in res]
         return res
+
+    def _hexpire(self, key: CommandItem, when_ms: int, *args: bytes) -> List[int]:
+        # Deal with input arguments
+        (nx, xx, gt, lt), left_args = extract_args(
+            args, ("nx", "xx", "gt", "lt"), left_from_first_unexpected=True, error_on_unexpected=False
+        )
+        if (nx, xx, gt, lt).count(True) > 1:
+            raise SimpleError(msgs.NX_XX_GT_LT_ERROR_MSG)
+        if len(left_args) < 3 or not casematch(left_args[0], b"fields"):
+            raise SimpleError(msgs.WRONG_ARGS_MSG6.format("HEXPIRE"))
+        num_fields = Int.decode(left_args[1])
+        if num_fields != len(left_args) - 2:
+            raise SimpleError(msgs.HEXPIRE_NUMFIELDS_DIFFERENT)
+        hash_val: Hash = key.value
+        if hash_val is None:
+            return [-2] * num_fields
+        fields = left_args[2:]
+        # process command
+        res = []
+        for field in fields:
+            if field not in hash_val:
+                res.append(-2)
+                continue
+            current_expiration = hash_val.get_key_expireat(field)
+            if (
+                (nx and current_expiration is not None)
+                or (xx and current_expiration is None)
+                or (gt and (current_expiration is None or when_ms <= current_expiration))
+                or (lt and current_expiration is not None and when_ms >= current_expiration)
+            ):
+                res.append(0)
+                continue
+            res.append(hash_val.set_key_expireat(field, when_ms))
+        return res
+
+    def _get_expireat(self, command: bytes, key: CommandItem, *args: bytes) -> List[int]:
+        if len(args) < 3 or not casematch(args[0], b"fields"):
+            raise SimpleError(msgs.WRONG_ARGS_MSG6.format(command))
+        num_fields = Int.decode(args[1])
+        if num_fields != len(args) - 2:
+            raise SimpleError(msgs.HEXPIRE_NUMFIELDS_DIFFERENT)
+        hash_val: Hash = key.value
+        if hash_val is None:
+            return [-2] * num_fields
+        fields = args[2:]
+        res = list()
+        for field in fields:
+            if field not in hash_val:
+                res.append(-2)
+                continue
+            when_ms = hash_val.get_key_expireat(field)
+            if when_ms is None:
+                res.append(-1)
+            else:
+                res.append(when_ms)
+        return res
+
+    @command(name="HEXPIRE", fixed=(Key(Hash), Int), repeat=(bytes,))
+    def hexpire(self, key: CommandItem, seconds: int, *args: bytes) -> List[int]:
+        when_ms = current_time() + seconds * 1000
+        return self._hexpire(key, when_ms, *args)
+
+    @command(name="HPEXPIRE", fixed=(Key(Hash), Int), repeat=(bytes,))
+    def hpexpire(self, key: CommandItem, milliseconds: int, *args: bytes) -> List[int]:
+        when_ms = current_time() + milliseconds
+        return self._hexpire(key, when_ms, *args)
+
+    @command(name="HEXPIREAT", fixed=(Key(Hash), Int), repeat=(bytes,))
+    def hexpireat(self, key: CommandItem, unix_time_seconds: int, *args: bytes) -> List[int]:
+        when_ms = unix_time_seconds * 1000
+        return self._hexpire(key, when_ms, *args)
+
+    @command(name="HPEXPIREAT", fixed=(Key(Hash), Int), repeat=(bytes,))
+    def hpexpireat(self, key: CommandItem, unix_time_ms: int, *args: bytes) -> List[int]:
+        return self._hexpire(key, unix_time_ms, *args)
+
+    @command(name="HPERSIST", fixed=(Key(Hash),), repeat=(bytes,))
+    def hpersist(self, key: CommandItem, *args: bytes) -> List[int]:
+        if len(args) < 3 or not casematch(args[0], b"fields"):
+            raise SimpleError(msgs.WRONG_ARGS_MSG6.format("HEXPIRE"))
+        num_fields = Int.decode(args[1])
+        if num_fields != len(args) - 2:
+            raise SimpleError(msgs.HEXPIRE_NUMFIELDS_DIFFERENT)
+        fields = args[2:]
+        hash_val: Hash = key.value
+        res = list()
+        for field in fields:
+            if field not in hash_val:
+                res.append(-2)
+                continue
+            if hash_val.clear_key_expireat(field):
+                res.append(1)
+            else:
+                res.append(-1)
+        return res
+
+    @command(name="HEXPIRETIME", fixed=(Key(Hash),), repeat=(bytes,), flags=msgs.FLAG_DO_NOT_CREATE)
+    def hexpiretime(self, key: CommandItem, *args: bytes) -> List[int]:
+        res = self._get_expireat(b"HEXPIRETIME", key, *args)
+        return [(i // 1000 if i > 0 else i) for i in res]
+
+    @command(name="HPEXPIRETIME", fixed=(Key(Hash),), repeat=(bytes,))
+    def hpexpiretime(self, key: CommandItem, *args: bytes) -> List[float]:
+        res = self._get_expireat(b"HEXPIRETIME", key, *args)
+        return res
+
+    @command(name="HTTL", fixed=(Key(Hash),), repeat=(bytes,))
+    def httl(self, key: CommandItem, *args: bytes) -> List[int]:
+        curr_expireat_ms = self._get_expireat(b"HEXPIRETIME", key, *args)
+        curr_time_ms = current_time()
+        return [((i - curr_time_ms) // 1000) if i > 0 else i for i in curr_expireat_ms]
+
+    @command(name="HPTTL", fixed=(Key(Hash),), repeat=(bytes,))
+    def hpttl(self, key: CommandItem, *args: bytes) -> List[int]:
+        curr_expireat_ms = self._get_expireat(b"HEXPIRETIME", key, *args)
+        curr_time_ms = current_time()
+        return [(i - curr_time_ms) if i > 0 else i for i in curr_expireat_ms]


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 66681c06..09abeb12 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -55,7 +55,7 @@ jobs:
       max-parallel: 8
       fail-fast: false
       matrix:
-        redis-image: [ "redis:6.2.14", "redis:7.0.15", "redis:7.2.4" ]
+        redis-image: [ "redis:6.2.14", "redis:7.0.15", "redis:7.4.0" ]
         python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
         redis-py: [ "4.3.6", "4.6.0", "5.0.8", "5.1.0b7" ]
         include:
@@ -98,6 +98,10 @@ jobs:
           python -m pip --quiet install poetry
           echo "$HOME/.poetry/bin" >> $GITHUB_PATH
           poetry install
+          # if python version is below 3.10 and redis-py is 5.0.9 - change it to 5.0.8
+          if [[ ${{ matrix.python-version }} != "3.10" && ${{ matrix.redis-py }} == "5.0.9" ]]; then
+              poetry run pip install redis==5.0.8
+          fi
           poetry run pip install redis==${{ matrix.redis-py }}
       - name: Install json
         if: ${{ matrix.extra }}
diff --git a/test/test_mixins/test_hash_commands.py b/test/test_mixins/test_hash_commands.py
index e2ad1360..8b18f185 100644
--- a/test/test_mixins/test_hash_commands.py
+++ b/test/test_mixins/test_hash_commands.py
@@ -1,3 +1,5 @@
+from typing import Optional, Dict
+
 import pytest
 import redis
 import redis.client
diff --git a/test/test_mixins/test_hash_expire_commands.py b/test/test_mixins/test_hash_expire_commands.py
new file mode 100644
index 00000000..713cb8e3
--- /dev/null
+++ b/test/test_mixins/test_hash_expire_commands.py
@@ -0,0 +1,358 @@
+import time
+from datetime import datetime, timedelta
+from typing import Optional, Dict
+
+import pytest
+import redis.client
+
+from test import testtools
+
+pytest.mark.min_server("7.4")
+testtools.run_test_if_redispy_ver("gte", "5")
+
+
+@pytest.mark.parametrize(
+    "expiration_seconds,preset_expiration,flags,expected_result",
+    [
+        # No flags
+        (100, None, dict(), 1),
+        (100, 50, dict(), 1),
+        # NX
+        (100, None, dict(nx=True), 1),
+        (100, 50, dict(nx=True), 0),
+        # XX
+        (100, None, dict(xx=True), 0),
+        (100, 50, dict(xx=True), 1),
+        # GT
+        (100, None, dict(gt=True), 0),
+        (100, 50, dict(gt=True), 1),
+        (100, 100, dict(gt=True), 0),
+        (100, 200, dict(gt=True), 0),
+        # LT
+        (100, None, dict(lt=True), 1),
+        (100, 50, dict(lt=True), 0),
+        (100, 100, dict(lt=True), 0),
+        (100, 200, dict(lt=True), 1),
+    ],
+)
+def test_hexpire(
+    r: redis.Redis,
+    expiration_seconds: int,
+    preset_expiration: Optional[int],
+    flags: Dict[str, bool],
+    expected_result: int,
+) -> None:
+    key, field = "redis-key", "hash-key"
+    r.hset(key, field, "value")
+    if preset_expiration is not None:
+        assert r.hexpire(key, preset_expiration, field) == [1]
+    result = r.hexpire(key, expiration_seconds, field, **flags)
+    assert result == [expected_result]
+
+
+@pytest.mark.slow
+def test_hexpire_basic(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    assert r.hexpire("redis-key", 1, "field1") == [1]
+    time.sleep(1.1)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is True
+
+
+def test_hexpire_conditions(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1"})
+    assert r.hexpire("redis-key", 2, "field1", xx=True) == [0]
+    assert r.hexpire("redis-key", 2, "field1", nx=True) == [1]
+    assert r.hexpire("redis-key", 1, "field1", xx=True) == [1]
+    assert r.hexpire("redis-key", 2, "field1", nx=True) == [0]
+    time.sleep(1.1)
+    assert r.hexists("redis-key", "field1") is False
+    r.hset("redis-key", "field1", "value1")
+    r.hexpire("redis-key", 2, "field1")
+    assert r.hexpire("redis-key", 1, "field1", gt=True) == [0]
+    assert r.hexpire("redis-key", 1, "field1", lt=True) == [1]
+    time.sleep(1.1)
+    assert r.hexists("redis-key", "field1") is False
+
+
+def test_hexpire_nonexistent_key_or_field(r: redis.Redis):
+    r.delete("redis-key")
+    assert r.hexpire("redis-key", 1, "field1") == [-2]
+    r.hset("redis-key", "field1", "value1")
+    assert r.hexpire("redis-key", 1, "nonexistent_field") == [-2]
+
+
+def test_hexpire_multiple_fields(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset(
+        "redis-key",
+        mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
+    )
+    assert r.hexpire("redis-key", 1, "field1", "field2") == [1, 1]
+    time.sleep(1.1)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is False
+    assert r.hexists("redis-key", "field3") is True
+
+
+def test_hpexpire_basic(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    assert r.hpexpire("redis-key", 100, "field1") == [1]
+    time.sleep(0.1)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is True
+
+
+def test_hpexpire_with_timedelta(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    assert r.hpexpire("redis-key", timedelta(milliseconds=500), "field1") == [1]
+    time.sleep(0.6)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is True
+
+
+def test_hpexpire_conditions(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1"})
+    assert r.hpexpire("redis-key", 1500, "field1", xx=True) == [0]
+    assert r.hpexpire("redis-key", 1500, "field1", nx=True) == [1]
+    assert r.hpexpire("redis-key", 500, "field1", xx=True) == [1]
+    assert r.hpexpire("redis-key", 1500, "field1", nx=True) == [0]
+    time.sleep(0.6)
+    assert r.hexists("redis-key", "field1") is False
+    r.hset("redis-key", "field1", "value1")
+    r.hpexpire("redis-key", 1000, "field1")
+    assert r.hpexpire("redis-key", 500, "field1", gt=True) == [0]
+    assert r.hpexpire("redis-key", 500, "field1", lt=True) == [1]
+    time.sleep(0.6)
+    assert r.hexists("redis-key", "field1") is False
+
+
+def test_hpexpire_nonexistent_key_or_field(r: redis.Redis):
+    r.delete("redis-key")
+    assert r.hpexpire("redis-key", 500, "field1") == [-2]
+    r.hset("redis-key", "field1", "value1")
+    assert r.hpexpire("redis-key", 500, "nonexistent_field") == [-2]
+
+
+def test_hpexpire_multiple_fields(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset(
+        "redis-key",
+        mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
+    )
+    assert r.hpexpire("redis-key", 500, "field1", "field2") == [1, 1]
+    time.sleep(0.6)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is False
+    assert r.hexists("redis-key", "field3") is True
+
+
+def test_hpexpire_multiple_condition_flags_error(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1"})
+    with pytest.raises(ValueError) as e:
+        r.hpexpire("redis-key", 500, "field1", nx=True, xx=True)
+    assert "Only one of" in str(e)
+
+
+def test_hexpireat_basic(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
+    assert r.hexpireat("redis-key", exp_time, "field1") == [1]
+    time.sleep(1.1)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is True
+
+
+def test_hexpireat_with_datetime(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    exp_time = datetime.now() + timedelta(seconds=1)
+    assert r.hexpireat("redis-key", exp_time, "field1") == [1]
+    time.sleep(1.1)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is True
+
+
+def test_hexpireat_conditions(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1"})
+    future_exp_time = int((datetime.now() + timedelta(seconds=20)).timestamp())
+    past_exp_time = int((datetime.now() - timedelta(seconds=10)).timestamp())
+    assert r.hexpireat("redis-key", future_exp_time, "field1", xx=True) == [0]
+    assert r.hexpireat("redis-key", future_exp_time, "field1", nx=True) == [1]
+    assert r.hexpireat("redis-key", past_exp_time, "field1", gt=True) == [0]
+    assert r.hexpireat("redis-key", past_exp_time, "field1", lt=True) == [2]
+    assert r.hexists("redis-key", "field1") is False
+
+
+def test_hexpireat_nonexistent_key_or_field(r: redis.Redis):
+    r.delete("redis-key")
+    future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
+    assert r.hexpireat("redis-key", future_exp_time, "field1") == [-2]
+    r.hset("redis-key", "field1", "value1")
+    assert r.hexpireat("redis-key", future_exp_time, "nonexistent_field") == [-2]
+
+
+def test_hexpireat_multiple_fields(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset(
+        "redis-key",
+        mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
+    )
+    exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
+    assert r.hexpireat("redis-key", exp_time, "field1", "field2") == [1, 1]
+    time.sleep(1.1)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is False
+    assert r.hexists("redis-key", "field3") is True
+
+
+def test_hexpireat_multiple_condition_flags_error(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1"})
+    exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
+    with pytest.raises(ValueError) as e:
+        r.hexpireat("redis-key", exp_time, "field1", nx=True, xx=True)
+    assert "Only one of" in str(e)
+
+
+def test_hpexpireat_basic(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000)
+    assert r.hpexpireat("redis-key", exp_time, "field1") == [1]
+    time.sleep(0.5)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is True
+
+
+def test_hpexpireat_with_datetime(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    exp_time = datetime.now() + timedelta(milliseconds=400)
+    assert r.hpexpireat("redis-key", exp_time, "field1") == [1]
+    time.sleep(0.5)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is True
+
+
+def test_hpexpireat_conditions(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1"})
+    future_exp_time = int((datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000)
+    past_exp_time = int((datetime.now() - timedelta(milliseconds=500)).timestamp() * 1000)
+    assert r.hpexpireat("redis-key", future_exp_time, "field1", xx=True) == [0]
+    assert r.hpexpireat("redis-key", future_exp_time, "field1", nx=True) == [1]
+    assert r.hpexpireat("redis-key", past_exp_time, "field1", gt=True) == [0]
+    assert r.hpexpireat("redis-key", past_exp_time, "field1", lt=True) == [2]
+    assert r.hexists("redis-key", "field1") is False
+
+
+def test_hpexpireat_nonexistent_key_or_field(r: redis.Redis):
+    r.delete("redis-key")
+    future_exp_time = int((datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000)
+    assert r.hpexpireat("redis-key", future_exp_time, "field1") == [-2]
+    r.hset("redis-key", "field1", "value1")
+    assert r.hpexpireat("redis-key", future_exp_time, "nonexistent_field") == [-2]
+
+
+def test_hpexpireat_multiple_fields(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset(
+        "redis-key",
+        mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
+    )
+    exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000)
+    assert r.hpexpireat("redis-key", exp_time, "field1", "field2") == [1, 1]
+    time.sleep(0.5)
+    assert r.hexists("redis-key", "field1") is False
+    assert r.hexists("redis-key", "field2") is False
+    assert r.hexists("redis-key", "field3") is True
+
+
+def test_hpexpireat_multiple_condition_flags_error(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1"})
+    exp_time = int((datetime.now() + timedelta(milliseconds=500)).timestamp())
+    with pytest.raises(ValueError) as e:
+        r.hpexpireat("redis-key", exp_time, "field1", nx=True, xx=True)
+    assert "Only one of" in str(e)
+
+
+def test_hpersist_multiple_fields(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    r.hexpire("redis-key", 5000, "field1")
+    assert r.hpersist("redis-key", "field1", "field2", "field3") == [1, -1, -2]
+
+
+def test_hpersist_nonexistent_key(r: redis.Redis):
+    r.delete("redis-key")
+    assert r.hpersist("redis-key", "field1", "field2", "field3") == [-2, -2, -2]
+
+
+def test_hexpiretime_multiple_fields_mixed_conditions(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
+    r.hexpireat("redis-key", future_time, "field1")
+    result = r.hexpiretime("redis-key", "field1", "field2", "field3")
+    assert future_time - 10 < result[0] <= future_time
+    assert result[1:] == [-1, -2]
+
+
+def test_hexpiretime_nonexistent_key(r: redis.Redis):
+    r.delete("redis-key")
+    assert r.hexpiretime("redis-key", "field1", "field2", "field3") == [-2, -2, -2]
+
+
+def test_hpexpiretime_multiple_fields_mixed_conditions(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
+    r.hexpireat("redis-key", future_time, "field1")
+    result = r.hpexpiretime("redis-key", "field1", "field2", "field3")
+    assert future_time * 1000 - 10000 < result[0] <= future_time * 1000
+    assert result[1:] == [-1, -2]
+
+
+def test_hpexpiretime_nonexistent_key(r: redis.Redis):
+    r.delete("redis-key")
+    assert r.hpexpiretime("redis-key", "field1", "field2", "field3") == [-2, -2, -2]
+
+
+def test_httl_multiple_fields_mixed_conditions(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
+    r.hexpireat("redis-key", future_time, "field1")
+    result = r.httl("redis-key", "field1", "field2", "field3")
+    assert 30 * 60 - 10 < result[0] <= 30 * 60
+    assert result[1:] == [-1, -2]
+
+
+def test_httl_nonexistent_key(r: redis.Redis):
+    r.delete("redis-key")
+    assert r.httl("redis-key", "field1", "field2", "field3") == [-2, -2, -2]
+
+
+def test_hpttl_multiple_fields_mixed_conditions(r: redis.Redis):
+    r.delete("redis-key")
+    r.hset("redis-key", mapping={"field1": "value1", "field2": "value2"})
+    future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
+    r.hexpireat("redis-key", future_time, "field1")
+    result = r.hpttl("redis-key", "field1", "field2", "field3")
+    assert 30 * 60000 - 10000 < result[0] <= 30 * 60000
+    assert result[1:] == [-1, -2]
+
+
+def test_hpttl_nonexistent_key(r: redis.Redis):
+    r.delete("redis-key")
+    assert r.hpttl("redis-key", "field1", "field2", "field3") == [-2, -2, -2]
diff --git a/test/test_mixins/test_string_commands.py b/test/test_mixins/test_string_commands.py
index a2ad96bb..7291826e 100644
--- a/test/test_mixins/test_string_commands.py
+++ b/test/test_mixins/test_string_commands.py
@@ -8,6 +8,7 @@
 import redis.client
 from redis.exceptions import ResponseError
 
+from fakeredis._helpers import current_time
 from ..testtools import raw_command
 
 
@@ -245,7 +246,7 @@ def test_set_exat(r: redis.Redis):
 
 @pytest.mark.min_server("6.2")
 def test_set_pxat(r: redis.Redis):
-    curr_time = int(time.time() * 1000)
+    curr_time = current_time()
     assert r.set("foo", "bar", pxat=curr_time + 100) is True
     assert r.get("foo") == b"bar"
     time.sleep(0.15)