1 Commits

Author SHA1 Message Date
hellerve 76b2bd7f6b add recursive defs 2021-10-27 21:13:39 -04:00
7 changed files with 4879 additions and 6615 deletions
+6 -46
View File
@@ -1,60 +1,20 @@
# redis # redis
A Redis client library for Carp, supporting Redis 7.x. is a Redis client library for Carp.
## Installation ## Installation
```clojure ```clojure
(load "https://git.veitheller.de/carpentry/redis.git@master") (load "https://veitheller.de/git/carpentry/redis.git@master")
``` ```
## Usage ## Usage
```clojure This package contains functions to convert to and from the Redis protocol,
(load "redis.carp") and handling Redis connections and commands. The documentation lives
[here](https://veitheller.de/redis).
(defn main [] You can also look at the examples in the [`examples`](./examples) directory.
(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/>
+183 -205
View File
@@ -9,7 +9,7 @@
<body> <body>
<div class="content"> <div class="content">
<div class="logo"> <div class="logo">
<a href="https://git.veitheller.de/carpentry/redis"> <a href="https://veitheller/git/carpentry/redis">
<img src=""> <img src="">
</a> </a>
<div class="title"> <div class="title">
@@ -30,240 +30,218 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="module"> <h1>
<h1> RESP
RESP </h1>
</h1> <div class="module-description">
<div class="module-description"> <p>is a wrapper around the <a href="https://redis.io/topics/protocol">Redis Serialization
<p>is a wrapper around the <a href="https://redis.io/topics/protocol">Redis Serialization Protocol</a>. You can create all types—though
Protocol</a>. 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 <a href="#str"><code>str</code></a>, and decode from stringify the built types into strings using <a href="#str"><code>str</code></a>, and decoding from
the string protocol using <a href="#from-string"><code>from-string</code></a>. Arrays are fully the string protocol using <a href="#from-string"><code>from-string</code></a>.</p>
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>
<div class="binder"> <p class="sig">
<a class="anchor" href="#Arr"> (Fn [(Array String)] RESP)
<h3 id="Arr"> </p>
Arr <span>
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [(Array (Box RESP))] RESP)
</p>
<span>
</span> </span>
<p class="doc"> <p class="doc">
<p>creates a <code>Arr</code>.</p> <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>
<div class="binder"> <p class="sig">
<a class="anchor" href="#Err"> (Fn [String] RESP)
<h3 id="Err"> </p>
Err <span>
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [String] RESP)
</p>
<span>
</span> </span>
<p class="doc"> <p class="doc">
<p>creates a <code>Err</code>.</p> <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>
<div class="binder"> <p class="sig">
<a class="anchor" href="#Integer"> (Fn [Int] RESP)
<h3 id="Integer"> </p>
Integer <span>
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [Int] RESP)
</p>
<span>
</span> </span>
<p class="doc"> <p class="doc">
<p>creates a <code>Integer</code>.</p> <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>
<div class="binder"> <p class="sig">
<a class="anchor" href="#Null"> (Fn [] RESP)
<h3 id="Null"> </p>
Null <span>
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [] RESP)
</p>
<span>
</span> </span>
<p class="doc"> <p class="doc">
<p>creates a <code>Null</code>.</p> <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>
<div class="binder"> <p class="sig">
<a class="anchor" href="#Str"> (Fn [String] RESP)
<h3 id="Str"> </p>
Str <span>
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [String] RESP)
</p>
<span>
</span> </span>
<p class="doc"> <p class="doc">
<p>creates a <code>Str</code>.</p> <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>
<div class="binder"> <p class="sig">
<a class="anchor" href="#copy"> (Fn [(Ref RESP a)] RESP)
<h3 id="copy"> </p>
copy <span>
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [(Ref RESP a)] RESP)
</p>
<span>
</span> </span>
<p class="doc"> <p class="doc">
<p>copies a <code>RESP</code>.</p> <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>
<div class="binder"> <p class="sig">
<a class="anchor" href="#delete"> (Fn [(Ref String a)] (Result RESP String))
<h3 id="delete"> </p>
delete <pre class="args">
</h3> (from-string s)
</a> </pre>
<div class="description"> <p class="doc">
instantiate <p>converts a RESP string into a <code>RESP</code> data structure.</p>
</div>
<p class="sig">
(Fn [RESP] ())
</p>
<span>
</span> </p>
<p class="doc"> </div>
<p>deletes a <code>RESP</code>. This should usually not be called manually.</p> <div class="binder">
<a class="anchor" href="#get-tag">
</p> <h3 id="get-tag">
get-tag
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<div class="binder"> <p class="sig">
<a class="anchor" href="#from-string"> (Fn [(Ref RESP a)] Int)
<h3 id="from-string"> </p>
from-string <span>
</h3>
</a>
<div class="description">
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> </span>
<p class="doc">
<p>Gets the tag from a <code>RESP</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#prn">
<h3 id="prn">
prn
</h3>
</a>
<div class="description">
instantiate
</div> </div>
<div class="binder"> <p class="sig">
<a class="anchor" href="#get-tag"> (Fn [(Ref RESP a)] String)
<h3 id="get-tag"> </p>
get-tag <span>
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [(Ref RESP a)] Int)
</p>
<span>
</span> </span>
<p class="doc"> <p class="doc">
<p>Gets the tag from a <code>RESP</code>.</p> <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> </div>
<div class="binder"> <p class="sig">
<a class="anchor" href="#prn"> (Fn [(Ref RESP a)] String)
<h3 id="prn"> </p>
prn <pre class="args">
</h3> (str r)
</a> </pre>
<div class="description"> <p class="doc">
instantiate <p>converts a <code>RESP</code> to a string.</p>
</div>
<p class="sig">
(Fn [(Ref RESP a)] String)
</p>
<span>
</span> </p>
<p class="doc">
<p>converts a <code>RESP</code> to a string.</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>
+4572 -6107
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -8,7 +8,7 @@
</head> </head>
<body> <body>
<div class="content"> <div class="content">
<a href="https://git.veitheller.de/carpentry/redis"> <a href="https://veitheller/git/carpentry/redis">
<div class="logo"> <div class="logo">
<img src="" alt="Logo"> <img src="" alt="Logo">
<div class="index"> <div class="index">
@@ -30,8 +30,9 @@
<h1> <h1>
redis redis
</h1> </h1>
<p>is a Redis client library for Carp.</p> <p>is a collection of modules for talking
<pre><code>(load &quot;https://git.veitheller.de/carpentry/redis.git@master&quot;) to Redis.</p>
<pre><code>(load &quot;https://veitheller/git/carpentry/redis@master&quot;)
</code></pre> </code></pre>
</div> </div>
+14 -64
View File
@@ -1,72 +1,22 @@
(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
; basic connectivity (Redis.send &r "PING" [])
(println* &(Redis.ping &r)) (println* &(Redis.read &r))
(println* &(Redis.echo &r @"hello from carp!"))
; strings (Redis.send &r "PING" [(Box (to-redis @"hiiiii"))])
(println* &(Redis.set &r @"language" @"carp")) (println* &(Redis.read &r))
(println* &(Redis.get &r @"language"))
; lists — array responses are proper nested RESP values (println* &(Redis.echo &r @"hi"))
(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"))
; hashes (println* &(Redis.rpush &r @"mylist" @"1"))
(println* &(Redis.hset &r @"user:1" @"name" @"alice")) (println* &(Redis.rpush &r @"mylist" @"2"))
(println* &(Redis.hset &r @"user:1" @"role" @"admin")) (println* &(Redis.lrange &r @"mylist" @"-100" @"100"))
(println* &(Redis.hgetall &r @"user:1"))
; sets (println* &(Redis.latency-help &r))
(println* &(Redis.sadd &r @"tags" @"functional"))
(println* &(Redis.sadd &r @"tags" @"systems"))
(println* &(Redis.sadd &r @"tags" @"compiled"))
(println* &(Redis.smembers &r @"tags"))
; counters (Redis.close r))
(println* &(Redis.set &r @"hits" @"0")) (Result.Error err) (IO.errorln &err)))
(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)))
+4 -3
View File
@@ -3,12 +3,13 @@
(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://git.veitheller.de/carpentry/redis") (Project.config "docs-url" "https://veitheller/git/carpentry/redis")
(Project.config "docs-styling" "../style.css") (Project.config "docs-styling" "../style.css")
(Project.config "docs-prelude" "is a Redis client library for Carp. (Project.config "docs-prelude" "is a collection of modules for talking
to Redis.
``` ```
(load \"https://git.veitheller.de/carpentry/redis.git@master\") (load \"https://veitheller/git/carpentry/redis@master\")
``` ```
") ")
+83 -174
View File
@@ -11,10 +11,6 @@
(defmodule RESP (defmodule RESP
(use-all Array Maybe Pattern Result) (use-all Array Maybe Pattern Result)
(hidden c)
(private c)
(def c (prn &@&(Arr [(Box.init (Null))])))
(defn str [r] (defn str [r]
(match @r (match @r
(Null) @"$-1\r\n" (Null) @"$-1\r\n"
@@ -22,86 +18,84 @@
(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) (Arr a)
(let-do [parts [(fmt "*%d\r\n" (Array.length &a))]] (fmt "*%d\r\n%s"
(for [i 0 (Array.length &a)] (Array.length &a)
(Array.push-back! &parts (str (Box.peek (Array.unsafe-nth &a i))))) &(String.concat &(Array.copy-map &(fn [b] (str (unbox b))) &a)))))
(String.concat &parts))))
(hidden decode-one) (hidden decode-bulk-string)
(private decode-one) (private decode-bulk-string)
(defn decode-one [s pos] (defn decode-bulk-string [s]
(if (>= pos (String.length s)) (if (starts-with? s "-1\r\n")
(Success (Pair.init (Null) pos)) (Success (Pair 4 (Null)))
(case (String.char-at s pos) (let [splt (split #"\r\n" s)
\+ (let [rest-start (+ pos 1) l (unsafe-first &splt)]
idx (String.index-of-from s \return rest-start)] (match (from-string l)
(if (= idx -1) (Nothing) (Error @"Error decoding bulk string: does not start with length!")
(Error @"Malformed simple string") (Just il)
(Success (Pair.init (Success
(Str (String.slice s rest-start idx)) (Pair (+ il 3)
(+ idx 2))))) (Str
\- (let [rest-start (+ pos 1) (String.prefix &(join "\r\n" &(suffix &splt 1)) il))))))))
idx (String.index-of-from s \return rest-start)]
(if (= idx -1) (private from-string-)
(Error @"Malformed error string") (hidden from-string-)
(Success (Pair.init (sig from-string- (Fn [&String] (Result (Pair Int RESP) String)))
(Err (String.slice s rest-start idx)) (defn from-string- [s] (Error @"dummy"))
(+ idx 2)))))
\: (let [rest-start (+ pos 1) (hidden decode-arr)
idx (String.index-of-from s \return rest-start)] (private decode-arr)
(if (= idx -1) (defn decode-arr [is]
(Error @"Malformed integer") (if (starts-with? &is "0\r\n")
(match (Int.from-string &(String.slice s rest-start idx)) (Success (Pair 4 (Arr [])))
(Maybe.Nothing) (Error @"Could not parse integer in result.") (let [splt (split #"\r\n" &(chomp &is))
(Maybe.Just n) (Success (Pair.init sl (unsafe-first &splt)
(Integer n) s (join "\r\n" &(suffix &splt 1))]
(+ idx 2)))))) (match (from-string sl)
\$ (let [rest-start (+ pos 1) (Nothing)
idx (String.index-of-from s \return rest-start)] (Error @"Error decoding array: does not start with length!")
(if (= idx -1) (Just l)
(Error @"Malformed bulk string") (let-do [a (Array.allocate l)
(match (Int.from-string &(String.slice s rest-start idx)) consumed 0
(Maybe.Nothing) (Error @"Error decoding bulk string: does not start with length!") err @""]
(Maybe.Just len) (for [i 0 (- l 1)]
(if (= len -1) (match (from-string- &s)
(Success (Pair.init (Null) (+ idx 2))) (Error msg)
(let [data-start (+ idx 2)] (do
(Success (Pair.init (set! err msg)
(Str (String.slice s data-start (+ data-start len))) (break))
(+ data-start (+ len 2))))))))) (Success p)
\* (let [rest-start (+ pos 1) (do
idx (String.index-of-from s \return rest-start)] (+= consumed @(Pair.a &p))
(if (= idx -1) (aset-uninitialized! &a i (Box @(Pair.b &p)))
(Error @"Malformed array") (set! s (String.suffix &s @(Pair.a &p))))))
(match (Int.from-string &(String.slice s rest-start idx)) (if (= &err "")
(Maybe.Nothing) (Error @"Error decoding array: does not start with length!") (Success (Pair consumed (Arr a)))
(Maybe.Just len) (Error err)))))))
(if (= len -1)
(Success (Pair.init (Null) (+ idx 2))) (defn from-string- [s]
(if (= len 0) (if (empty? s)
(Success (Pair.init (Arr []) (+ idx 2))) (Success (Pair 0 (Null)))
(let-do [cur (+ idx 2) (case (head s)
a (Array.allocate len) \+
err @""] (let [f (unsafe-first &(split #"\r\n" &(tail s)))]
(for [i 0 len] (Success (Pair (String.length f) (Str @f))))
(match (decode-one s cur) \-
(Result.Error e) (do (set! err e) (break)) (let [f (unsafe-first &(split #"\r\n" &(tail s)))]
(Result.Success p) (Success (Pair (String.length f) (Err @f))))
(do \:
(aset-uninitialized! &a i (Box.init @(Pair.a &p))) (let [f (unsafe-first &(split #"\r\n" &(tail s)))]
(set! cur @(Pair.b &p))))) (match (from-string f)
(if (= &err "") (Maybe.Nothing)
(Success (Pair.init (Arr a) cur)) (Error @"Could not parse integer in result.")
(Error err)))))))) (Maybe.Just i)
(Error (fmt "Malformed RESP data: got %s" &(String.slice s pos (+ pos 1))))))) (Success (Pair (String.length f) (Integer i)))))
\$ (decode-bulk-string &(tail s))
\* (decode-arr (tail s))
(Error (fmt "Malformed RESP data: got %s" s)))))
(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) (Result.map (from-string- s) &(fn [p] @(Pair.b &p))))
(Success (Null))
(match (decode-one s 0)
(Result.Error e) (Error e)
(Result.Success p) (Success @(Pair.a &p)))))
(definterface to-redis (Fn [a] RESP)) (definterface to-redis (Fn [a] RESP))
) )
@@ -139,8 +133,9 @@ 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]
(let [cmd-parts (copy-map &(fn [x] (Box.init (to-redis @x))) &(Pattern.split #" " &cmd))] (if (empty? &args)
(Socket.send (sock r) &(str &(RESP.Arr (concat &[cmd-parts (copy-map &(fn [x] (Box.init @x)) args)])))))) (Socket.send (sock r) &(fmt "%s\r\n" cmd))
(Socket.send (sock r) &(str &(RESP.Arr (concat &[[(Box (to-redis @cmd))] 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)))
@@ -155,7 +150,7 @@ For variable port numbers please check out [`open-on`](#open-on).")
(defndynamic rconv- [args] (defndynamic rconv- [args]
(if (= (length args) 0) (if (= (length args) 0)
(array) (array)
(cons (list 'to-redis (car args)) (rconv- (cdr args))))) (cons (list 'Box (list 'to-redis (car args))) (rconv- (cdr args)))))
(defmacro defredis [cmd :rest args] (defmacro defredis [cmd :rest args]
(eval (eval
@@ -168,7 +163,7 @@ It takes the same arguments as the [Redis command](https://redis.io/commands/"
])) ]))
(list 'defn cmd (collect-into (cons 'r args) array) (list 'defn cmd (collect-into (cons 'r args) array)
(list 'do (list 'do
(list 'Redis.send 'r (list 'copy (rtreat- (Symbol.str cmd))) (list 'ref (rconv- args))) (list 'Redis.send 'r (rtreat- (Symbol.str cmd)) (rconv- args))
'(Redis.read r)))))) '(Redis.read r))))))
; these commands were scraped from redis.io on the 9th of Feb 2020 ; these commands were scraped from redis.io on the 9th of Feb 2020
@@ -414,102 +409,16 @@ 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, Protocol](https://redis.io/topics/protocol). You can create all types—though
stringify the built types into strings using [`str`](#str), and decode from creating arrays is a little unsightly due to the absence of recursive types—,
the string protocol using [`from-string`](#from-string). Arrays are fully stringify the built types into strings using [`str`](#str), and decoding from
supported, including nested arrays. the string protocol using [`from-string`](#from-string).
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))`.")