5 Commits

Author SHA1 Message Date
hellerve fafa2b40d2 use new sockets lib 2026-04-11 11:20:25 +02:00
hellerve 692fe51728 update docs 2026-04-03 11:37:12 +02:00
hellerve d88b0631a6 clarify redis version 2026-04-03 11:07:53 +02:00
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 6727 additions and 4894 deletions
+46 -6
View File
@@ -1,20 +1,60 @@
# redis
is a Redis client library for Carp.
A Redis client library for Carp, supporting Redis 7.x.
## Installation
```clojure
(load "https://veitheller.de/git/carpentry/redis.git@master")
(load "https://git.veitheller.de/carpentry/redis.git@0.2.0")
```
## Usage
This package contains functions to convert to and from the Redis protocol,
and handling Redis connections and commands. The documentation lives
[here](https://veitheller.de/redis).
```clojure
(load "redis.carp")
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/>
+224 -183
View File
@@ -9,7 +9,7 @@
<body>
<div class="content">
<div class="logo">
<a href="https://veitheller/git/carpentry/redis">
<a href="https://git.veitheller.de/carpentry/redis">
<img src="">
</a>
<div class="title">
@@ -30,218 +30,259 @@
</ul>
</div>
</div>
<h1>
RESP
</h1>
<div class="module-description">
<p>is a wrapper around the <a href="https://redis.io/topics/protocol">Redis Serialization
Protocol</a>. You can create all types—though
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 decoding from
the string protocol using <a href="#from-string"><code>from-string</code></a>.</p>
<div class="module">
<h1>
RESP
</h1>
<div class="module-description">
<p>is a wrapper around the <a href="https://redis.io/topics/protocol">Redis Serialization
Protocol</a>. You can create all types,
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>. Arrays are fully
supported, including nested arrays.</p>
<pre><code>; decoding
(RESP.from-string &quot;+OK\r\n&quot;) ; =&gt; (Success (Str @&quot;OK&quot;))
(RESP.from-string &quot;:42\r\n&quot;) ; =&gt; (Success (Integer 42))
(RESP.from-string &quot;$-1\r\n&quot;) ; =&gt; (Success (Null))
; encoding
(str &amp;(RESP.Str @&quot;hi&quot;)) ; =&gt; &quot;$2\r\nhi\r\n&quot;
(str &amp;(RESP.Integer 42)) ; =&gt; &quot;:42\r\n&quot;
; pattern matching on responses
(match (Redis.get &amp;r @&quot;key&quot;)
(Result.Success resp)
(match resp
(RESP.Str s) (println* &quot;got: &quot; &amp;s)
(RESP.Null) (println* &quot;not found&quot;)
(RESP.Arr items) (println* &quot;array of &quot; &amp;(Int.str (Array.length &amp;items)))
_ (println* &quot;other&quot;))
(Result.Error e) (println* &quot;error: &quot; &amp;e))
</code></pre>
<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>
</div>
<div class="binder">
<a class="anchor" href="#Arr">
<h3 id="Arr">
Arr
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [(Array String)] RESP)
</p>
<span>
<div class="binder">
<a class="anchor" href="#Arr">
<h3 id="Arr">
Arr
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [(Array (Box RESP))] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Arr</code>.</p>
</span>
<p class="doc">
<p>creates a <code>Arr</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#Err">
<h3 id="Err">
Err
</h3>
</a>
<div class="description">
instantiate
</p>
</div>
<p class="sig">
(Fn [String] RESP)
</p>
<span>
<div class="binder">
<a class="anchor" href="#Err">
<h3 id="Err">
Err
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [String] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Err</code>.</p>
</span>
<p class="doc">
<p>creates a <code>Err</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#Integer">
<h3 id="Integer">
Integer
</h3>
</a>
<div class="description">
instantiate
</p>
</div>
<p class="sig">
(Fn [Int] RESP)
</p>
<span>
<div class="binder">
<a class="anchor" href="#Integer">
<h3 id="Integer">
Integer
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [Int] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Integer</code>.</p>
</span>
<p class="doc">
<p>creates a <code>Integer</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#Null">
<h3 id="Null">
Null
</h3>
</a>
<div class="description">
instantiate
</p>
</div>
<p class="sig">
(Fn [] RESP)
</p>
<span>
<div class="binder">
<a class="anchor" href="#Null">
<h3 id="Null">
Null
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Null</code>.</p>
</span>
<p class="doc">
<p>creates a <code>Null</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#Str">
<h3 id="Str">
Str
</h3>
</a>
<div class="description">
instantiate
</p>
</div>
<p class="sig">
(Fn [String] RESP)
</p>
<span>
<div class="binder">
<a class="anchor" href="#Str">
<h3 id="Str">
Str
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [String] RESP)
</p>
<span>
</span>
<p class="doc">
<p>creates a <code>Str</code>.</p>
</span>
<p class="doc">
<p>creates a <code>Str</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#copy">
<h3 id="copy">
copy
</h3>
</a>
<div class="description">
instantiate
</p>
</div>
<p class="sig">
(Fn [(Ref RESP a)] RESP)
</p>
<span>
<div class="binder">
<a class="anchor" href="#copy">
<h3 id="copy">
copy
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [(Ref RESP a)] RESP)
</p>
<span>
</span>
<p class="doc">
<p>copies a <code>RESP</code>.</p>
</span>
<p class="doc">
<p>copies a <code>RESP</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#from-string">
<h3 id="from-string">
from-string
</h3>
</a>
<div class="description">
defn
</p>
</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>
<div class="binder">
<a class="anchor" href="#delete">
<h3 id="delete">
delete
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [RESP] ())
</p>
<span>
</p>
</div>
<div class="binder">
<a class="anchor" href="#get-tag">
<h3 id="get-tag">
get-tag
</h3>
</a>
<div class="description">
instantiate
</span>
<p class="doc">
<p>deletes a <code>RESP</code>. This should usually not be called manually.</p>
</p>
</div>
<p class="sig">
(Fn [(Ref RESP a)] Int)
</p>
<span>
<div class="binder">
<a class="anchor" href="#from-string">
<h3 id="from-string">
from-string
</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>
</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
</p>
</div>
<p class="sig">
(Fn [(Ref RESP a)] String)
</p>
<span>
<div class="binder">
<a class="anchor" href="#get-tag">
<h3 id="get-tag">
get-tag
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [(Ref RESP a)] Int)
</p>
<span>
</span>
<p class="doc">
<p>converts a <code>RESP</code> to a string.</p>
</span>
<p class="doc">
<p>Gets the tag from a <code>RESP</code>.</p>
</p>
</div>
<div class="binder">
<a class="anchor" href="#str">
<h3 id="str">
str
</h3>
</a>
<div class="description">
defn
</p>
</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>
<div class="binder">
<a class="anchor" href="#prn">
<h3 id="prn">
prn
</h3>
</a>
<div class="description">
instantiate
</div>
<p class="sig">
(Fn [(Ref RESP a)] String)
</p>
<span>
</p>
</span>
<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>
</body>
+6117 -4574
View File
File diff suppressed because it is too large Load Diff
+22 -4
View File
@@ -8,7 +8,7 @@
</head>
<body>
<div class="content">
<a href="https://veitheller/git/carpentry/redis">
<a href="https://git.veitheller.de/carpentry/redis">
<div class="logo">
<img src="" alt="Logo">
<div class="index">
@@ -30,9 +30,27 @@
<h1>
redis
</h1>
<p>is a collection of modules for talking
to Redis.</p>
<pre><code>(load &quot;https://veitheller/git/carpentry/redis@master&quot;)
<p>is a Redis client library for Carp, supporting Redis 7.x.</p>
<h2>Installation</h2>
<pre><code>(load &quot;https://git.veitheller.de/carpentry/redis.git@0.2.0&quot;)
</code></pre>
<h2>Example</h2>
<pre><code>(defn main []
(match (Redis.open &quot;127.0.0.1&quot;)
(Result.Success r)
(do
(println* &amp;(Redis.set &amp;r @&quot;key&quot; @&quot;value&quot;))
(println* &amp;(Redis.get &amp;r @&quot;key&quot;))
(println* &amp;(Redis.lrange &amp;r @&quot;mylist&quot; @&quot;0&quot; @&quot;-1&quot;))
(match (Redis.get &amp;r @&quot;key&quot;)
(Result.Success resp)
(match resp
(RESP.Str s) (println* &quot;got: &quot; &amp;s)
(RESP.Null) (println* &quot;not found&quot;)
_ (println* &quot;unexpected type&quot;))
(Result.Error e) (println* &quot;error: &quot; &amp;e))
(Redis.close r))
(Result.Error err) (IO.errorln &amp;err)))
</code></pre>
</div>
+64 -14
View File
@@ -1,22 +1,72 @@
(load "redis.carp")
(defn main []
(match (Redis.open "127.0.0.1")
(Result.Success r)
(do
(Redis.send &r @"PING" &[])
(println* &(Redis.read &r))
(match (Redis.open "127.0.0.1")
(Result.Success r)
(do
; basic connectivity
(println* &(Redis.ping &r))
(println* &(Redis.echo &r @"hello from carp!"))
(Redis.send &r @"PING" &[(to-redis @"hiiiii")])
(println* &(Redis.read &r))
; strings
(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"))
(println* &(Redis.rpush &r @"mylist" @"2"))
(println* &(Redis.lrange &r @"mylist" @"-100" @"100"))
; hashes
(println* &(Redis.hset &r @"user:1" @"name" @"alice"))
(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))
(Result.Error err) (IO.errorln &err)))
; counters
(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)))
+26 -4
View File
@@ -3,13 +3,35 @@
(Project.config "title" "redis")
(Project.config "docs-directory" "./docs/")
(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-prelude" "is a collection of modules for talking
to Redis.
(Project.config "docs-prelude" "is a Redis client library for Carp, supporting Redis 7.x.
## Installation
```
(load \"https://veitheller/git/carpentry/redis@master\")
(load \"https://git.veitheller.de/carpentry/redis.git@0.2.0\")
```
## Example
```
(defn main []
(match (Redis.open \"127.0.0.1\")
(Result.Success r)
(do
(println* &(Redis.set &r @\"key\" @\"value\"))
(println* &(Redis.get &r @\"key\"))
(println* &(Redis.lrange &r @\"mylist\" @\"0\" @\"-1\"))
(match (Redis.get &r @\"key\")
(Result.Success resp)
(match resp
(RESP.Str s) (println* \"got: \" &s)
(RESP.Null) (println* \"not found\")
_ (println* \"unexpected type\"))
(Result.Error e) (println* \"error: \" &e))
(Redis.close r))
(Result.Error err) (IO.errorln &err)))
```
")
+215 -96
View File
@@ -1,12 +1,11 @@
(load "git@github.com:carpentry-org/sockets@0.0.2")
(load "git@github.com:carpentry-org/socket@0.1.1")
(deftype RESP
(Null [])
(Str [String])
(Err [String])
(Integer [Int])
;(Arr [(Array &RESP)])
(Arr [(Array String)])
(Arr [(Array (Box RESP))])
)
(defmodule RESP
@@ -14,7 +13,7 @@
(hidden c)
(private c)
(def c (prn &@&[@""]))
(def c (prn &@&(Arr [(Box.init (Null))])))
(defn str [r]
(match @r
@@ -22,87 +21,87 @@
(Str s) (fmt "$%d\r\n%s\r\n" (String.length &s) &s)
(Err s) (fmt "-%s\r\n" &s)
(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)
(private decode-bulk-string)
(defn decode-bulk-string [s]
(if (starts-with? s "-1\r\n")
(Success (Null))
(let [splt (split #"\r\n" s)
l (unsafe-first &splt)]
(match (from-string l)
(Nothing) (Error @"Error decoding bulk string: does not start with length!")
(Just il)
(Success (Str
(String.prefix &(join "\r\n" &(suffix &splt 1)) il)))))))
(hidden agg)
(private agg)
(defn agg [els len]
(let-do [consumed 0
clen 0]
(for [i 0 len]
(let [el (unsafe-nth els i)]
(if (>= clen len)
(break)
(do
(set! consumed (inc consumed))
(set! clen (+ 2 (+ clen (String.length el))))))))
consumed))
(hidden decode-arr)
(private decode-arr)
(defn decode-arr [s]
(if (starts-with? s "*0\r\n")
(Success (Null))
(let [splt (split #"\r\n" &(chomp s))
sl (unsafe-first &splt)]
(match (from-string sl)
(Nothing)
(Error @"Error decoding array: does not start with length!")
(Just l)
(let-do [a (Array.allocate l)
idx 0
err ""]
(for [i 0 (- (length &splt) 1)]
; TODO: have nested structures
(let-do [el (unsafe-nth &splt (+ i 1))]
(case (head el)
\$
(match (from-string &(tail el))
(Maybe.Nothing)
(aset-uninitialized! &a idx @"")
(Maybe.Just il)
(let-do [rest (suffix &splt (+ i 2))]
(aset-uninitialized! &a idx (String.prefix &(join "\r\n" &rest) il))
(set! i (+ i (agg &rest il)))))
\*
(do
(set! err "TODO: cannot deal with nested arrays")
(break))
(aset-uninitialized! &a idx (chomp el)))
(set! idx (inc idx))))
(if (= err "")
(Success (Arr a))
(Error @err)))))))
(hidden decode-one)
(private decode-one)
(defn decode-one [s pos]
(if (>= pos (String.length s))
(Success (Pair.init (Null) pos))
(case (String.char-at s pos)
\+ (let [rest-start (+ pos 1)
idx (String.index-of-from s \return rest-start)]
(if (= idx -1)
(Error @"Malformed simple string")
(Success (Pair.init
(Str (String.slice s rest-start idx))
(+ idx 2)))))
\- (let [rest-start (+ pos 1)
idx (String.index-of-from s \return rest-start)]
(if (= idx -1)
(Error @"Malformed error string")
(Success (Pair.init
(Err (String.slice s rest-start idx))
(+ idx 2)))))
\: (let [rest-start (+ pos 1)
idx (String.index-of-from s \return rest-start)]
(if (= idx -1)
(Error @"Malformed integer")
(match (Int.from-string &(String.slice s rest-start idx))
(Maybe.Nothing) (Error @"Could not parse integer in result.")
(Maybe.Just n) (Success (Pair.init
(Integer n)
(+ idx 2))))))
\$ (let [rest-start (+ pos 1)
idx (String.index-of-from s \return rest-start)]
(if (= idx -1)
(Error @"Malformed bulk string")
(match (Int.from-string &(String.slice s rest-start idx))
(Maybe.Nothing) (Error @"Error decoding bulk string: does not start with length!")
(Maybe.Just len)
(if (= len -1)
(Success (Pair.init (Null) (+ idx 2)))
(let [data-start (+ idx 2)]
(Success (Pair.init
(Str (String.slice s data-start (+ data-start len)))
(+ data-start (+ len 2)))))))))
\* (let [rest-start (+ pos 1)
idx (String.index-of-from s \return rest-start)]
(if (= idx -1)
(Error @"Malformed array")
(match (Int.from-string &(String.slice s rest-start idx))
(Maybe.Nothing) (Error @"Error decoding array: does not start with length!")
(Maybe.Just len)
(if (= len -1)
(Success (Pair.init (Null) (+ idx 2)))
(if (= len 0)
(Success (Pair.init (Arr []) (+ idx 2)))
(let-do [cur (+ idx 2)
a (Array.allocate len)
err @""]
(for [i 0 len]
(match (decode-one s cur)
(Result.Error e) (do (set! err e) (break))
(Result.Success p)
(do
(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.")
(defn from-string [s]
(if (empty? s)
(Success (Null))
(case (head s)
\+ (Success (Str @(unsafe-first &(split #"\r\n" &(tail s)))))
\- (Success (Err @(unsafe-first &(split #"\r\n" &(tail s)))))
\:
(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)))))
(match (decode-one s 0)
(Result.Error e) (Error e)
(Result.Success p) (Success @(Pair.a &p)))))
(definterface to-redis (Fn [a] RESP))
)
@@ -118,18 +117,17 @@
)
(deftype Redis [
sock Socket
sock TcpStream
])
(defmodule Redis
(use-all Array Result Socket)
(use-all Array Result)
(doc open-on "opens the connection to Redis on port `port`.")
(defn open-on [host port]
(let [s (setup-client host port)]
(if (valid? &s)
(Success (init s))
(Error (fmt "Couldnt connect to %s:%d" host port)))))
(match (TcpStream.connect host port)
(Result.Success s) (Success (init s))
(Result.Error e) (Error (fmt "Couldnt connect to %s:%d: %s" host port &e))))
(doc open "opens the connection to Redis on port 6379.
@@ -137,15 +135,19 @@ For variable port numbers please check out [`open-on`](#open-on).")
(defn open [host] (open-on host 6379))
(doc read "reads a `RESP` object from Redis.")
(defn read [r] (RESP.from-string &(Socket.read (sock r))))
(defn read [r]
(match (the (Result String String) (TcpStream.read (sock r)))
(Result.Success s) (RESP.from-string &s)
(Result.Error e) (Error e)))
(doc send "sends the command `cmd` with the arguments `args` to Redis.")
(defn send [r cmd args]
(if (empty? args)
(Socket.send (sock r) &(fmt "%s\r\n" &cmd))
(Socket.send (sock r) &(str &(RESP.Arr (concat &[[(str &(to-redis cmd))] (copy-map &RESP.str args)]))))))
(let [cmd-parts (copy-map &(fn [x] (Box.init (to-redis @x))) &(Pattern.split #" " &cmd))
msg (str &(RESP.Arr (concat &[cmd-parts (copy-map &(fn [x] (Box.init @x)) args)])))]
(ignore (TcpStream.send (sock r) &msg))))
(doc close "closes the connection to Redis.")
(defn close [r] (Socket.close @(sock &r)))
(defn close [r] (TcpStream.close @(sock &r)))
)
(defndynamic rtreat- [s]
@@ -416,16 +418,133 @@ It takes the same arguments as the [Redis command](https://redis.io/commands/"
(defredis latency-latest)
(defredis latency-reset)
(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
connection using [`open`](#open) or [`open-on`](#open-on), reading from and
sending to the connection (using [`read`](#read) and [`send`](#send),
respectively), and contains thin wrappers around all Redis commands (everything
else).")
respectively), and contains thin wrappers around all Redis commands through 7.2.
```
(match (Redis.open \"127.0.0.1\")
(Result.Success r)
(do
(println* &(Redis.set &r @\"key\" @\"val\"))
(println* &(Redis.get &r @\"key\"))
(println* &(Redis.lrange &r @\"list\" @\"0\" @\"-1\"))
(Redis.close r))
(Result.Error err) (IO.errorln &err))
```")
(doc RESP "is a wrapper around the [Redis Serialization
Protocol](https://redis.io/topics/protocol). You can create all types—though
creating arrays is a little unsightly due to the absence of recursive types—,
stringify the built types into strings using [`str`](#str), and decoding from
the string protocol using [`from-string`](#from-string).
Protocol](https://redis.io/topics/protocol). You can create all types,
stringify the built types into strings using [`str`](#str), and decode from
the string protocol using [`from-string`](#from-string). Arrays are fully
supported, including nested arrays.
```
; decoding
(RESP.from-string \"+OK\\r\\n\") ; => (Success (Str @\"OK\"))
(RESP.from-string \":42\\r\\n\") ; => (Success (Integer 42))
(RESP.from-string \"$-1\\r\\n\") ; => (Success (Null))
; encoding
(str &(RESP.Str @\"hi\")) ; => \"$2\\r\\nhi\\r\\n\"
(str &(RESP.Integer 42)) ; => \":42\\r\\n\"
; pattern matching on responses
(match (Redis.get &r @\"key\")
(Result.Success resp)
(match resp
(RESP.Str s) (println* \"got: \" &s)
(RESP.Null) (println* \"not found\")
(RESP.Arr items) (println* \"array of \" &(Int.str (Array.length &items)))
_ (println* \"other\"))
(Result.Error e) (println* \"error: \" &e))
```
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))`.")