2 Commits

Author SHA1 Message Date
hellerve 0b64365e39 add nested types to redis 2026-04-03 10:57:06 +02:00
hellerve ed02b8aadb update url 2021-10-27 21:17:16 -04:00
7 changed files with 6610 additions and 4881 deletions
+46 -6
View File
@@ -1,20 +1,60 @@
# redis # redis
is a Redis client library for Carp. A Redis client library for Carp, supporting Redis 7.x.
## Installation ## Installation
```clojure ```clojure
(load "https://veitheller.de/git/carpentry/redis.git@master") (load "https://git.veitheller.de/carpentry/redis.git@master")
``` ```
## Usage ## Usage
This package contains functions to convert to and from the Redis protocol, ```clojure
and handling Redis connections and commands. The documentation lives (load "redis.carp")
[here](https://veitheller.de/redis).
You can also look at the examples in the [`examples`](./examples) directory. (defn main []
(match (Redis.open "127.0.0.1")
(Result.Success r)
(do
(println* &(Redis.ping &r))
(println* &(Redis.set &r @"key" @"value"))
(println* &(Redis.get &r @"key"))
; list operations return nested RESP arrays
(println* &(Redis.rpush &r @"mylist" @"a"))
(println* &(Redis.rpush &r @"mylist" @"b"))
(println* &(Redis.lrange &r @"mylist" @"0" @"-1"))
; pattern match on responses
(match (Redis.get &r @"key")
(Result.Success resp)
(match resp
(RESP.Str s) (println* "got: " &s)
(RESP.Null) (println* "key not found")
_ (println* "unexpected type"))
(Result.Error e) (println* "error: " &e))
(Redis.close r))
(Result.Error err) (IO.errorln &err)))
```
The library provides thin wrappers around all standard Redis commands through
7.2, so you can call them directly (e.g. `Redis.get`, `Redis.hset`,
`Redis.lrange`). For commands with complex or variadic arguments, use
`Redis.send` directly:
```clojure
(Redis.send &r @"SET" &[(to-redis @"key") (to-redis @"value")])
(Redis.read &r)
```
The RESP type supports recursive arrays via `Box`, so nested structures
(e.g. from `XRANGE` or `COMMAND`) are decoded faithfully.
Full API documentation lives [here](https://veitheller.de/redis).
More examples are in the [`examples`](./examples) directory.
<hr/> <hr/>
+212 -190
View File
@@ -9,7 +9,7 @@
<body> <body>
<div class="content"> <div class="content">
<div class="logo"> <div class="logo">
<a href="https://veitheller/git/carpentry/redis"> <a href="https://git.veitheller.de/carpentry/redis">
<img src=""> <img src="">
</a> </a>
<div class="title"> <div class="title">
@@ -30,218 +30,240 @@
</ul> </ul>
</div> </div>
</div> </div>
<h1> <div class="module">
RESP <h1>
</h1> RESP
<div class="module-description"> </h1>
<p>is a wrapper around the <a href="https://redis.io/topics/protocol">Redis Serialization <div class="module-description">
Protocol</a>. You can create all types—though <p>is a wrapper around the <a href="https://redis.io/topics/protocol">Redis Serialization
creating arrays is a little unsightly due to the absence of recursive types, Protocol</a>. You can create all types,
stringify the built types into strings using <a href="#str"><code>str</code></a>, and decoding from stringify the built types into strings using <a href="#str"><code>str</code></a>, and decode from
the string protocol using <a href="#from-string"><code>from-string</code></a>.</p> the string protocol using <a href="#from-string"><code>from-string</code></a>. Arrays are fully
supported, including nested arrays.</p>
<p>If you want your types to be supported when encoding, youll have to implement <p>If you want your types to be supported when encoding, youll have to implement
the interface <code>to-redis</code>, the signature of which is <code>(Fn [a] RESP))</code>.</p> the interface <code>to-redis</code>, the signature of which is <code>(Fn [a] RESP))</code>.</p>
</div>
<div class="binder">
<a class="anchor" href="#Arr">
<h3 id="Arr">
Arr
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<p class="sig"> <div class="binder">
(Fn [(Array String)] RESP) <a class="anchor" href="#Arr">
</p> <h3 id="Arr">
<span> Arr
</h3>
</span> </a>
<p class="doc"> <div class="description">
<p>creates a <code>Arr</code>.</p> instantiate
</div>
<p class="sig">
(Fn [(Array (Box RESP))] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Arr</code>.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#Err">
<h3 id="Err">
Err
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<p class="sig"> <div class="binder">
(Fn [String] RESP) <a class="anchor" href="#Err">
</p> <h3 id="Err">
<span> Err
</h3>
</span> </a>
<p class="doc"> <div class="description">
<p>creates a <code>Err</code>.</p> instantiate
</div>
<p class="sig">
(Fn [String] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Err</code>.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#Integer">
<h3 id="Integer">
Integer
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<p class="sig"> <div class="binder">
(Fn [Int] RESP) <a class="anchor" href="#Integer">
</p> <h3 id="Integer">
<span> Integer
</h3>
</span> </a>
<p class="doc"> <div class="description">
<p>creates a <code>Integer</code>.</p> instantiate
</div>
<p class="sig">
(Fn [Int] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Integer</code>.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#Null">
<h3 id="Null">
Null
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<p class="sig"> <div class="binder">
(Fn [] RESP) <a class="anchor" href="#Null">
</p> <h3 id="Null">
<span> Null
</h3>
</span> </a>
<p class="doc"> <div class="description">
<p>creates a <code>Null</code>.</p> instantiate
</div>
<p class="sig">
(Fn [] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Null</code>.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#Str">
<h3 id="Str">
Str
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<p class="sig"> <div class="binder">
(Fn [String] RESP) <a class="anchor" href="#Str">
</p> <h3 id="Str">
<span> Str
</h3>
</span> </a>
<p class="doc"> <div class="description">
<p>creates a <code>Str</code>.</p> instantiate
</div>
<p class="sig">
(Fn [String] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Str</code>.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#copy">
<h3 id="copy">
copy
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<p class="sig"> <div class="binder">
(Fn [(Ref RESP a)] RESP) <a class="anchor" href="#copy">
</p> <h3 id="copy">
<span> copy
</h3>
</span> </a>
<p class="doc"> <div class="description">
<p>copies a <code>RESP</code>.</p> instantiate
</div>
<p class="sig">
(Fn [(Ref RESP a)] RESP)
</p>
<span>
</span>
<p class="doc">
<p>copies a <code>RESP</code>.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#from-string">
<h3 id="from-string">
from-string
</h3>
</a>
<div class="description">
defn
</div> </div>
<p class="sig"> <div class="binder">
(Fn [(Ref String a)] (Result RESP String)) <a class="anchor" href="#delete">
</p> <h3 id="delete">
<pre class="args"> delete
(from-string s) </h3>
</pre> </a>
<p class="doc"> <div class="description">
<p>converts a RESP string into a <code>RESP</code> data structure.</p> instantiate
</div>
<p class="sig">
(Fn [RESP] ())
</p>
<span>
</span>
<p class="doc">
<p>deletes a <code>RESP</code>. This should usually not be called manually.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#get-tag">
<h3 id="get-tag">
get-tag
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<p class="sig"> <div class="binder">
(Fn [(Ref RESP a)] Int) <a class="anchor" href="#from-string">
</p> <h3 id="from-string">
<span> from-string
</h3>
</span> </a>
<p class="doc"> <div class="description">
<p>Gets the tag from a <code>RESP</code>.</p> defn
</div>
<p class="sig">
(Fn [(Ref String a)] (Result RESP String))
</p>
<pre class="args">
(from-string s)
</pre>
<p class="doc">
<p>converts a RESP string into a <code>RESP</code> data structure.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#prn">
<h3 id="prn">
prn
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<p class="sig"> <div class="binder">
(Fn [(Ref RESP a)] String) <a class="anchor" href="#get-tag">
</p> <h3 id="get-tag">
<span> get-tag
</h3>
</span> </a>
<p class="doc"> <div class="description">
<p>converts a <code>RESP</code> to a string.</p> instantiate
</div>
<p class="sig">
(Fn [(Ref RESP a)] Int)
</p>
<span>
</span>
<p class="doc">
<p>Gets the tag from a <code>RESP</code>.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#str">
<h3 id="str">
str
</h3>
</a>
<div class="description">
defn
</div> </div>
<p class="sig"> <div class="binder">
(Fn [(Ref RESP a)] String) <a class="anchor" href="#prn">
</p> <h3 id="prn">
<pre class="args"> prn
(str r) </h3>
</pre> </a>
<p class="doc"> <div class="description">
<p>converts a <code>RESP</code> to a string.</p> instantiate
</div>
<p class="sig">
(Fn [(Ref RESP a)] String)
</p>
<span>
</span>
<p class="doc">
<p>converts a <code>RESP</code> to a string.</p>
</p> </p>
</div>
<div class="binder">
<a class="anchor" href="#str">
<h3 id="str">
str
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(Fn [(Ref RESP a)] String)
</p>
<pre class="args">
(str r)
</pre>
<p class="doc">
<p>converts a <code>RESP</code> to a string.</p>
</p>
</div>
</div> </div>
</div> </div>
</body> </body>
+6113 -4578
View File
File diff suppressed because it is too large Load Diff
+3 -4
View File
@@ -8,7 +8,7 @@
</head> </head>
<body> <body>
<div class="content"> <div class="content">
<a href="https://veitheller/git/carpentry/redis"> <a href="https://git.veitheller.de/carpentry/redis">
<div class="logo"> <div class="logo">
<img src="" alt="Logo"> <img src="" alt="Logo">
<div class="index"> <div class="index">
@@ -30,9 +30,8 @@
<h1> <h1>
redis redis
</h1> </h1>
<p>is a collection of modules for talking <p>is a Redis client library for Carp.</p>
to Redis.</p> <pre><code>(load &quot;https://git.veitheller.de/carpentry/redis.git@master&quot;)
<pre><code>(load &quot;https://veitheller/git/carpentry/redis@master&quot;)
</code></pre> </code></pre>
</div> </div>
+64 -14
View File
@@ -1,22 +1,72 @@
(load "redis.carp") (load "redis.carp")
(defn main [] (defn main []
(match (Redis.open "127.0.0.1") (match (Redis.open "127.0.0.1")
(Result.Success r) (Result.Success r)
(do (do
(Redis.send &r @"PING" &[]) ; basic connectivity
(println* &(Redis.read &r)) (println* &(Redis.ping &r))
(println* &(Redis.echo &r @"hello from carp!"))
(Redis.send &r @"PING" &[(to-redis @"hiiiii")]) ; strings
(println* &(Redis.read &r)) (println* &(Redis.set &r @"language" @"carp"))
(println* &(Redis.get &r @"language"))
(println* &(Redis.echo &r @"hi")) ; lists — array responses are proper nested RESP values
(println* &(Redis.rpush &r @"queue" @"task-a"))
(println* &(Redis.rpush &r @"queue" @"task-b"))
(println* &(Redis.rpush &r @"queue" @"task-c"))
(println* &(Redis.lrange &r @"queue" @"0" @"-1"))
(println* &(Redis.lpop &r @"queue"))
(println* &(Redis.llen &r @"queue"))
(println* &(Redis.rpush &r @"mylist" @"1")) ; hashes
(println* &(Redis.rpush &r @"mylist" @"2")) (println* &(Redis.hset &r @"user:1" @"name" @"alice"))
(println* &(Redis.lrange &r @"mylist" @"-100" @"100")) (println* &(Redis.hset &r @"user:1" @"role" @"admin"))
(println* &(Redis.hgetall &r @"user:1"))
(println* &(Redis.latency-help &r)) ; sets
(println* &(Redis.sadd &r @"tags" @"functional"))
(println* &(Redis.sadd &r @"tags" @"systems"))
(println* &(Redis.sadd &r @"tags" @"compiled"))
(println* &(Redis.smembers &r @"tags"))
(Redis.close r)) ; counters
(Result.Error err) (IO.errorln &err))) (println* &(Redis.set &r @"hits" @"0"))
(println* &(Redis.incr &r @"hits"))
(println* &(Redis.incrby &r @"hits" @"99"))
(println* &(Redis.get &r @"hits"))
; key expiry
(println* &(Redis.set &r @"session" @"abc123"))
(println* &(Redis.expire &r @"session" @"300"))
(println* &(Redis.ttl &r @"session"))
; pattern matching on RESP values
(match (Redis.lrange &r @"queue" @"0" @"-1")
(Result.Success resp)
(match resp
(RESP.Arr items)
(println* "remaining tasks: " &(Int.str (Array.length &items)))
_ (println* "unexpected response"))
(Result.Error e) (println* "error: " &e))
; streams — xrange returns nested arrays (array of [id, [field, value, ...]])
(println* &(Redis.xadd &r @"events" @"*" @"type" @"click"))
(println* &(Redis.xadd &r @"events" @"*" @"type" @"scroll"))
(println* &(Redis.xrange &r @"events" @"-" @"+"))
; missing key returns Null
(println* &(Redis.get &r @"nonexistent"))
; cleanup
(println* &(Redis.del &r @"language"))
(println* &(Redis.del &r @"queue"))
(println* &(Redis.del &r @"user:1"))
(println* &(Redis.del &r @"tags"))
(println* &(Redis.del &r @"hits"))
(println* &(Redis.del &r @"session"))
(println* &(Redis.del &r @"events"))
(Redis.close r))
(Result.Error err) (IO.errorln &err)))
+3 -4
View File
@@ -3,13 +3,12 @@
(Project.config "title" "redis") (Project.config "title" "redis")
(Project.config "docs-directory" "./docs/") (Project.config "docs-directory" "./docs/")
(Project.config "docs-logo" "") (Project.config "docs-logo" "")
(Project.config "docs-url" "https://veitheller/git/carpentry/redis") (Project.config "docs-url" "https://git.veitheller.de/carpentry/redis")
(Project.config "docs-styling" "../style.css") (Project.config "docs-styling" "../style.css")
(Project.config "docs-prelude" "is a collection of modules for talking (Project.config "docs-prelude" "is a Redis client library for Carp.
to Redis.
``` ```
(load \"https://veitheller/git/carpentry/redis@master\") (load \"https://git.veitheller.de/carpentry/redis.git@master\")
``` ```
") ")
+169 -85
View File
@@ -5,8 +5,7 @@
(Str [String]) (Str [String])
(Err [String]) (Err [String])
(Integer [Int]) (Integer [Int])
;(Arr [(Array &RESP)]) (Arr [(Array (Box RESP))])
(Arr [(Array String)])
) )
(defmodule RESP (defmodule RESP
@@ -14,7 +13,7 @@
(hidden c) (hidden c)
(private c) (private c)
(def c (prn &@&[@""])) (def c (prn &@&(Arr [(Box.init (Null))])))
(defn str [r] (defn str [r]
(match @r (match @r
@@ -22,87 +21,87 @@
(Str s) (fmt "$%d\r\n%s\r\n" (String.length &s) &s) (Str s) (fmt "$%d\r\n%s\r\n" (String.length &s) &s)
(Err s) (fmt "-%s\r\n" &s) (Err s) (fmt "-%s\r\n" &s)
(Integer i) (fmt ":%d\r\n" i) (Integer i) (fmt ":%d\r\n" i)
(Arr a) (fmt "*%d\r\n%s" (Array.length &a) &(String.concat &a)))) (Arr a)
(let-do [parts [(fmt "*%d\r\n" (Array.length &a))]]
(for [i 0 (Array.length &a)]
(Array.push-back! &parts (str (Box.peek (Array.unsafe-nth &a i)))))
(String.concat &parts))))
(hidden decode-bulk-string) (hidden decode-one)
(private decode-bulk-string) (private decode-one)
(defn decode-bulk-string [s] (defn decode-one [s pos]
(if (starts-with? s "-1\r\n") (if (>= pos (String.length s))
(Success (Null)) (Success (Pair.init (Null) pos))
(let [splt (split #"\r\n" s) (case (String.char-at s pos)
l (unsafe-first &splt)] \+ (let [rest-start (+ pos 1)
(match (from-string l) idx (String.index-of-from s \return rest-start)]
(Nothing) (Error @"Error decoding bulk string: does not start with length!") (if (= idx -1)
(Just il) (Error @"Malformed simple string")
(Success (Str (Success (Pair.init
(String.prefix &(join "\r\n" &(suffix &splt 1)) il))))))) (Str (String.slice s rest-start idx))
(+ idx 2)))))
(hidden agg) \- (let [rest-start (+ pos 1)
(private agg) idx (String.index-of-from s \return rest-start)]
(defn agg [els len] (if (= idx -1)
(let-do [consumed 0 (Error @"Malformed error string")
clen 0] (Success (Pair.init
(for [i 0 len] (Err (String.slice s rest-start idx))
(let [el (unsafe-nth els i)] (+ idx 2)))))
(if (>= clen len) \: (let [rest-start (+ pos 1)
(break) idx (String.index-of-from s \return rest-start)]
(do (if (= idx -1)
(set! consumed (inc consumed)) (Error @"Malformed integer")
(set! clen (+ 2 (+ clen (String.length el)))))))) (match (Int.from-string &(String.slice s rest-start idx))
consumed)) (Maybe.Nothing) (Error @"Could not parse integer in result.")
(Maybe.Just n) (Success (Pair.init
(hidden decode-arr) (Integer n)
(private decode-arr) (+ idx 2))))))
(defn decode-arr [s] \$ (let [rest-start (+ pos 1)
(if (starts-with? s "*0\r\n") idx (String.index-of-from s \return rest-start)]
(Success (Null)) (if (= idx -1)
(let [splt (split #"\r\n" &(chomp s)) (Error @"Malformed bulk string")
sl (unsafe-first &splt)] (match (Int.from-string &(String.slice s rest-start idx))
(match (from-string sl) (Maybe.Nothing) (Error @"Error decoding bulk string: does not start with length!")
(Nothing) (Maybe.Just len)
(Error @"Error decoding array: does not start with length!") (if (= len -1)
(Just l) (Success (Pair.init (Null) (+ idx 2)))
(let-do [a (Array.allocate l) (let [data-start (+ idx 2)]
idx 0 (Success (Pair.init
err ""] (Str (String.slice s data-start (+ data-start len)))
(for [i 0 (- (length &splt) 1)] (+ data-start (+ len 2)))))))))
; TODO: have nested structures \* (let [rest-start (+ pos 1)
(let-do [el (unsafe-nth &splt (+ i 1))] idx (String.index-of-from s \return rest-start)]
(case (head el) (if (= idx -1)
\$ (Error @"Malformed array")
(match (from-string &(tail el)) (match (Int.from-string &(String.slice s rest-start idx))
(Maybe.Nothing) (Maybe.Nothing) (Error @"Error decoding array: does not start with length!")
(aset-uninitialized! &a idx @"") (Maybe.Just len)
(Maybe.Just il) (if (= len -1)
(let-do [rest (suffix &splt (+ i 2))] (Success (Pair.init (Null) (+ idx 2)))
(aset-uninitialized! &a idx (String.prefix &(join "\r\n" &rest) il)) (if (= len 0)
(set! i (+ i (agg &rest il))))) (Success (Pair.init (Arr []) (+ idx 2)))
\* (let-do [cur (+ idx 2)
(do a (Array.allocate len)
(set! err "TODO: cannot deal with nested arrays") err @""]
(break)) (for [i 0 len]
(aset-uninitialized! &a idx (chomp el))) (match (decode-one s cur)
(set! idx (inc idx)))) (Result.Error e) (do (set! err e) (break))
(if (= err "") (Result.Success p)
(Success (Arr a)) (do
(Error @err))))))) (aset-uninitialized! &a i (Box.init @(Pair.a &p)))
(set! cur @(Pair.b &p)))))
(if (= &err "")
(Success (Pair.init (Arr a) cur))
(Error err))))))))
(Error (fmt "Malformed RESP data: got %s" &(String.slice s pos (+ pos 1)))))))
(doc from-string "converts a RESP string into a `RESP` data structure.") (doc from-string "converts a RESP string into a `RESP` data structure.")
(defn from-string [s] (defn from-string [s]
(if (empty? s) (if (empty? s)
(Success (Null)) (Success (Null))
(case (head s) (match (decode-one s 0)
\+ (Success (Str @(unsafe-first &(split #"\r\n" &(tail s))))) (Result.Error e) (Error e)
\- (Success (Err @(unsafe-first &(split #"\r\n" &(tail s))))) (Result.Success p) (Success @(Pair.a &p)))))
\:
(match (from-string (unsafe-first &(split #"\r\n" &(tail s))))
(Maybe.Nothing)
(Error @"Could not parse integer in result.")
(Maybe.Just i)
(Success (Integer i)))
\$ (decode-bulk-string &(tail s))
\* (decode-arr &(tail s))
(Error (fmt "Malformed RESP data: got %s" s)))))
(definterface to-redis (Fn [a] RESP)) (definterface to-redis (Fn [a] RESP))
) )
@@ -140,9 +139,8 @@ For variable port numbers please check out [`open-on`](#open-on).")
(defn read [r] (RESP.from-string &(Socket.read (sock r)))) (defn read [r] (RESP.from-string &(Socket.read (sock r))))
(doc send "sends the command `cmd` with the arguments `args` to Redis.") (doc send "sends the command `cmd` with the arguments `args` to Redis.")
(defn send [r cmd args] (defn send [r cmd args]
(if (empty? args) (let [cmd-parts (copy-map &(fn [x] (Box.init (to-redis @x))) &(Pattern.split #" " &cmd))]
(Socket.send (sock r) &(fmt "%s\r\n" &cmd)) (Socket.send (sock r) &(str &(RESP.Arr (concat &[cmd-parts (copy-map &(fn [x] (Box.init @x)) args)]))))))
(Socket.send (sock r) &(str &(RESP.Arr (concat &[[(str &(to-redis cmd))] (copy-map &RESP.str args)]))))))
(doc close "closes the connection to Redis.") (doc close "closes the connection to Redis.")
(defn close [r] (Socket.close @(sock &r))) (defn close [r] (Socket.close @(sock &r)))
@@ -416,16 +414,102 @@ It takes the same arguments as the [Redis command](https://redis.io/commands/"
(defredis latency-latest) (defredis latency-latest)
(defredis latency-reset) (defredis latency-reset)
(defredis latency-help) (defredis latency-help)
; commands added in Redis 6.0
(defredis acl-cat)
(defredis acl-deluser username)
(defredis acl-genpass)
(defredis acl-getuser username)
(defredis acl-list)
(defredis acl-load)
(defredis acl-log)
(defredis acl-save)
(defredis acl-setuser username rule)
(defredis acl-users)
(defredis acl-whoami)
(defredis client-caching mode)
(defredis client-getredir)
(defredis client-tracking mode)
(defredis client-trackinginfo)
(defredis hello)
(defredis reset)
(defredis lpos key element)
; commands added in Redis 6.2
(defredis copy source destination)
(defredis getdel key)
(defredis getex key)
(defredis lmove source destination wherefrom whereto)
(defredis blmove source destination wherefrom whereto timeout)
(defredis smismember key member)
(defredis zmscore key member)
(defredis zdiff numkeys key)
(defredis zdiffstore destination numkeys key)
(defredis zunion numkeys key)
(defredis zinter numkeys key)
(defredis zrangestore dst src min max)
(defredis zrandmember key)
(defredis hrandfield key)
(defredis geosearch key)
(defredis geosearchstore destination source)
(defredis xautoclaim key group consumer min-idle-time start)
(defredis xgroup-createconsumer key groupname consumername)
(defredis xgroup-delconsumer key groupname consumername)
; commands added in Redis 7.0
(defredis function-load library-code)
(defredis function-delete library-name)
(defredis function-dump)
(defredis function-restore serialized-value)
(defredis function-list)
(defredis function-stats)
(defredis function-flush)
(defredis fcall function numkeys)
(defredis fcall_ro function numkeys)
(defredis eval_ro script numkeys key)
(defredis evalsha_ro sha1 numkeys key)
(defredis sort_ro key)
(defredis lmpop numkeys key)
(defredis blmpop timeout numkeys key)
(defredis zmpop numkeys key)
(defredis bzmpop timeout numkeys key)
(defredis zintercard numkeys key)
(defredis sintercard numkeys key)
(defredis expiretime key)
(defredis pexpiretime key)
(defredis lcs key1 key2)
(defredis cluster-shards)
(defredis cluster-links)
(defredis cluster-addslotsrange slot)
(defredis cluster-delslotsrange slot)
(defredis client-no-evict mode)
(defredis ssubscribe channel)
(defredis sunsubscribe)
(defredis spublish channel message)
(defredis command-docs command-name)
(defredis command-list)
(defredis latency-histogram)
(defredis module-loadex path)
; commands added in Redis 7.2
(defredis client-setinfo attr value)
(defredis client-no-touch mode)
(defredis waitaof numlocal numreplicas timeout)
(doc Redis "is a wrapper around Redis connections. It supports opening a (doc Redis "is a wrapper around Redis connections. It supports opening a
connection using [`open`](#open) or [`open-on`](#open-on), reading from and connection using [`open`](#open) or [`open-on`](#open-on), reading from and
sending to the connection (using [`read`](#read) and [`send`](#send), sending to the connection (using [`read`](#read) and [`send`](#send),
respectively), and contains thin wrappers around all Redis commands (everything respectively), and contains thin wrappers around all Redis commands (everything
else).") else).")
(doc RESP "is a wrapper around the [Redis Serialization (doc RESP "is a wrapper around the [Redis Serialization
Protocol](https://redis.io/topics/protocol). You can create all types—though Protocol](https://redis.io/topics/protocol). You can create all types,
creating arrays is a little unsightly due to the absence of recursive types—, stringify the built types into strings using [`str`](#str), and decode from
stringify the built types into strings using [`str`](#str), and decoding from the string protocol using [`from-string`](#from-string). Arrays are fully
the string protocol using [`from-string`](#from-string). supported, including nested arrays.
If you want your types to be supported when encoding, youll have to implement If you want your types to be supported when encoding, youll have to implement
the interface `to-redis`, the signature of which is `(Fn [a] RESP))`.") the interface `to-redis`, the signature of which is `(Fn [a] RESP))`.")