Clojure library thanks to Ragnar Dahlén

This commit is contained in:
antirez
2009-06-14 23:15:21 +02:00
parent c9a111acf4
commit e59229a2d5
13 changed files with 1338 additions and 1 deletions

View File

@ -0,0 +1,127 @@
;(add-classpath "file:///Users/ragge/Projects/clojure/redis-clojure/src/")
(set! *warn-on-reflection* true)
(ns redis
(:refer-clojure :exclude [get set type keys sort])
(:use redis.internal))
(defmacro with-server
"Evaluates body in the context of a new connection to a Redis server
then closes the connection.
server-spec is a map with any of the following keys:
:host hostname (default \"127.0.0.1\")
:port port (default 6379)
:db database to use (default 0)"
[server-spec & body]
`(with-server* ~server-spec (fn []
(do
(redis/select (:db *server*))
~@body))))
;;
;; Reply conversion functions
;;
(defn int-to-bool
"Convert integer reply to a boolean value"
[int]
(= 1 int))
(defn string-to-keyword
"Convert a string reply to a keyword"
[string]
(keyword string))
(defn string-to-seq
"Convert a space separated string to a sequence of words"
[#^String string]
(if (empty? string)
nil
(re-seq #"\S+" string)))
(defn string-to-map
"Convert strings with format 'key:value\r\n'+ to a map with {key
value} pairs"
[#^String string]
(let [lines (.split string "(\\r\\n|:)")]
(apply hash-map lines)))
(defn int-to-date
"Return a Date representation of a UNIX timestamp"
[int]
(new java.util.Date (long int)))
(defn seq-to-set
[sequence]
(clojure.core/set sequence))
;;
;; Commands
;;
(defcommands
;; Connection handling
(auth [] :inline)
(quit [password] :inline)
(ping [] :inline)
;; String commands
(set [key value] :bulk)
(get [key] :inline)
(getset [key value] :bulk)
(setnx [key value] :bulk int-to-bool)
(incr [key] :inline)
(incrby [key integer] :inline)
(decr [key] :inline)
(decrby [key integer] :inline)
(exists [key] :inline int-to-bool)
(mget [key & keys] :inline)
(del [key] :inline int-to-bool)
;; Key space commands
(type [key] :inline string-to-keyword)
(keys [pattern] :inline string-to-seq)
(randomkey [] :inline)
(rename [oldkey newkey] :inline)
(renamenx [oldkey newkey] :inline int-to-bool)
(dbsize [] :inline)
(expire [key seconds] :inline int-to-bool)
(ttl [key] :inline)
;; List commands
(rpush [key value] :bulk)
(lpush [key value] :bulk)
(llen [key] :inline)
(lrange [key start end] :inline)
(ltrim [key start end] :inline)
(lindex [key index] :inline)
(lset [key index value] :bulk)
(lrem [key count value] :bulk)
(lpop [key] :inline)
(rpop [key] :inline)
;; Set commands
(sadd [key member] :bulk int-to-bool)
(srem [key member] :bulk int-to-bool)
(smove [srckey destkey member] :bulk int-to-bool)
(scard [key] :inline)
(sismember [key member] :bulk int-to-bool)
(sinter [key & keys] :inline seq-to-set)
(sinterstore [destkey key & keys] :inline)
(sunion [key & keys] :inline seq-to-set)
(sunionstore [destkey key & keys] :inline)
(sdiff [key & keys] :inline seq-to-set)
(sdiffstore [destkey key & keys] :inline)
(smembers [key] :inline seq-to-set)
;; Multiple database handling commands
(select [index] :inline)
(move [key dbindex] :inline)
(flushdb [] :inline)
(flushall [] :inline)
;; Sorting
(sort [key & options] :sort)
;; Persistence
(save [] :inline)
(bgsave [] :inline)
(lastsave [] :inline int-to-date)
(shutdown [] :inline)
(info [] :inline string-to-map)
;;(monitor [] :inline))
)

View File

@ -0,0 +1,263 @@
(ns redis.internal
(:import [java.io InputStream
OutputStream
Reader
InputStreamReader
BufferedReader]
[java.net Socket]))
(def *cr* 0x0d)
(def *lf* 0x0a)
(defn- cr? [c] (= c *cr*))
(defn- lf? [c] (= c *lf*))
(defn- uppercase [#^String s] (.toUpperCase s))
(defn- trim [#^String s] (.trim s))
(defn- parse-int [#^String s] (Integer/parseInt s))
(defn- char-array [len] (make-array Character/TYPE len))
(def *default-host* "127.0.0.1")
(def *default-port* 6379)
(def *default-db* 0)
(def *default-timeout* 5)
(defstruct server :host :port :db :timeout :socket)
(def *server* (struct-map server
:host *default-host*
:port *default-port*
:db *default-db*
:timeout *default-timeout* ;; not yet used
:socket nil))
(defn connect-to-server
"Create a Socket connected to server"
[server]
(let [{:keys [host port timeout]} server
socket (Socket. #^String host #^Integer port)]
(doto socket
(.setTcpNoDelay true))))
(defn with-server*
[server-spec func]
(let [server (merge *server* server-spec)]
(with-open [#^Socket socket (connect-to-server server)]
(binding [*server* (assoc server :socket socket)]
(func)))))
(defn socket* []
(or (:socket *server*)
(throw (Exception. "Not connected to a Redis server"))))
(defn send-command
"Send a command string to server"
[#^String cmd]
(let [out (.getOutputStream (#^Socket socket*))
bytes (.getBytes cmd)]
(.write out bytes)))
(defn read-crlf
"Read a CR+LF combination from Reader"
[#^Reader reader]
(let [cr (.read reader)
lf (.read reader)]
(when-not
(and (cr? cr)
(lf? lf))
(throw (Exception. "Error reading CR/LF")))
nil))
(defn read-line-crlf
"Read from reader until exactly a CR+LF combination is
found. Returns the line read without trailing CR+LF.
This is used instead of Reader.readLine() method since it tries to
read either a CR, a LF or a CR+LF, which we don't want in this
case."
[#^Reader reader]
(loop [line []
c (.read reader)]
(when (< c 0)
(throw (Exception. "Error reading line: EOF reached before CR/LF sequence")))
(if (cr? c)
(let [next (.read reader)]
(if (lf? next)
(apply str line)
(throw (Exception. "Error reading line: Missing LF"))))
(recur (conj line (char c))
(.read reader)))))
;;
;; Reply dispatching
;;
(defn reply-type
([#^BufferedReader reader]
(let [type (char (.read reader))]
type)))
(defmulti parse-reply reply-type :default :unknown)
(defn read-reply
([]
(let [input-stream (.getInputStream (#^Socket socket*))
reader (BufferedReader. (InputStreamReader. input-stream))]
(read-reply reader)))
([#^BufferedReader reader]
(parse-reply reader)))
(defmethod parse-reply :unknown
[#^BufferedReader reader]
(throw (Exception. (str "Unknown reply type:"))))
(defmethod parse-reply \-
[#^BufferedReader reader]
(let [error (read-line-crlf reader)]
(throw (Exception. (str "Server error: " error)))))
(defmethod parse-reply \+
[#^BufferedReader reader]
(read-line-crlf reader))
(defmethod parse-reply \$
[#^BufferedReader reader]
(let [line (read-line-crlf reader)
length (parse-int line)]
(if (< length 0)
nil
(let [#^chars cbuf (char-array length)
nread (.read reader cbuf 0 length)]
(if (not= nread length)
(throw (Exception. "Could not read correct number of bytes"))
(do
(read-crlf reader) ;; CRLF
(String. cbuf)))))))
(defmethod parse-reply \*
[#^BufferedReader reader]
(let [line (read-line-crlf reader)
count (parse-int line)]
(if (< count 0)
nil
(loop [i count
replies []]
(if (zero? i)
replies
(recur (dec i) (conj replies (read-reply reader))))))))
(defmethod parse-reply \:
[#^BufferedReader reader]
(let [line (trim (read-line-crlf reader))
int (parse-int line)]
int))
(defn str-join
"Join elements in sequence with separator"
[separator sequence]
(apply str (interpose separator sequence)))
(defn inline-command
"Create a string for an inline command"
[name & args]
(let [cmd (str-join " " (conj args name))]
(str cmd "\r\n")))
(defn bulk-command
"Create a string for an bulk command"
[name & args]
(let [data (str (last args))
data-length (count (str data))
args* (concat (butlast args) [data-length])
cmd (apply inline-command name args*)]
(str cmd data "\r\n")))
(defn- sort-command-args-to-string
[args]
(loop [arg-strings []
args args]
(if (empty? args)
(str-join " " arg-strings)
(let [type (first args)
args (rest args)]
(condp = type
:by (let [pattern (first args)]
(recur (conj arg-strings "BY" pattern)
(rest args)))
:limit (let [start (first args)
end (second args)]
(recur (conj arg-strings "LIMIT" start end)
(drop 2 args)))
:get (let [pattern (first args)]
(recur (conj arg-strings "GET" pattern)
(rest args)))
:alpha (recur (conj arg-strings "ALPHA") args)
:asc (recur (conj arg-strings "ASC") args)
:desc (recur (conj arg-strings "DESC") args)
(throw (Exception. (str "Error parsing SORT arguments: Unknown argument: " type))))))))
(defn sort-command
[name & args]
(when-not (= name "SORT")
(throw (Exception. "Sort command name must be 'SORT'")))
(let [key (first args)
arg-string (sort-command-args-to-string (rest args))
cmd (str "SORT " key)]
(if (empty? arg-string)
(str cmd "\r\n")
(str cmd " " arg-string "\r\n"))))
(def command-fns {:inline 'inline-command
:bulk 'bulk-command
:sort 'sort-command})
(defn parse-params
"Return a restructuring of params, which is of form:
[arg* (& more)?]
into
[(arg1 arg2 ..) more]"
[params]
(let [[args rest] (split-with #(not= % '&) params)]
[args (last rest)]))
(defmacro defcommand
"Define a function for Redis command name with parameters
params. Type is one of :inline or :bulk, which determines how the
command string is constructued."
([name params type] `(defcommand ~name ~params ~type (fn [reply#] reply#)))
([name params type reply-fn] `(~name ~params ~type ~reply-fn)
(do
(let [command (uppercase (str name))
command-fn (type command-fns)
[command-params
command-params-rest] (parse-params params)]
`(defn ~name
~params
(let [request# (apply ~command-fn
~command
~@command-params
~command-params-rest)]
(send-command request#)
(~reply-fn (read-reply)))))
)))
(defmacro defcommands
[& command-defs]
`(do ~@(map (fn [command-def]
`(defcommand ~@command-def)) command-defs)))

View File

@ -0,0 +1,387 @@
(ns redis.tests
(:refer-clojure :exclude [get set keys type sort])
(:require redis)
(:use [clojure.contrib.test-is]))
(defn server-fixture [f]
(redis/with-server
{:host "127.0.0.1"
:port 6379
:db 15}
;; String value
(redis/set "foo" "bar")
;; List with three items
(redis/rpush "list" "one")
(redis/rpush "list" "two")
(redis/rpush "list" "three")
;; Set with three members
(redis/sadd "set" "one")
(redis/sadd "set" "two")
(redis/sadd "set" "three")
(f)
(redis/flushdb)))
(use-fixtures :each server-fixture)
(deftest ping
(is (= "PONG" (redis/ping))))
(deftest set
(redis/set "bar" "foo")
(is (= "foo" (redis/get "bar")))
(redis/set "foo" "baz")
(is (= "baz" (redis/get "foo"))))
(deftest get
(is (= nil (redis/get "bar")))
(is (= "bar" (redis/get "foo"))))
(deftest getset
(is (= nil (redis/getset "bar" "foo")))
(is (= "foo" (redis/get "bar")))
(is (= "bar" (redis/getset "foo" "baz")))
(is (= "baz" (redis/get "foo"))))
(deftest mget
(is (= [nil] (redis/mget "bar")))
(redis/set "bar" "baz")
(redis/set "baz" "buz")
(is (= ["bar"] (redis/mget "foo")))
(is (= ["bar" "baz"] (redis/mget "foo" "bar")))
(is (= ["bar" "baz" "buz"] (redis/mget "foo" "bar" "baz")))
(is (= ["bar" nil "buz"] (redis/mget "foo" "bra" "baz")))
)
(deftest setnx
(is (= true (redis/setnx "bar" "foo")))
(is (= "foo" (redis/get "bar")))
(is (= false (redis/setnx "foo" "baz")))
(is (= "bar" (redis/get "foo"))))
(deftest incr
(is (= 1 (redis/incr "nonexistent")))
(is (= 1 (redis/incr "foo")))
(is (= 2 (redis/incr "foo"))))
(deftest incrby
(is (= 42 (redis/incrby "nonexistent" 42)))
(is (= 0 (redis/incrby "foo" 0)))
(is (= 5 (redis/incrby "foo" 5))))
(deftest decr
(is (= -1 (redis/decr "nonexistent")))
(is (= -1 (redis/decr "foo")))
(is (= -2 (redis/decr "foo"))))
(deftest decrby
(is (= -42 (redis/decrby "nonexistent" 42)))
(is (= 0 (redis/decrby "foo" 0)))
(is (= -5 (redis/decrby "foo" 5))))
(deftest exists
(is (= true (redis/exists "foo")))
(is (= false (redis/exists "nonexistent"))))
(deftest del
(is (= false (redis/del "nonexistent")))
(is (= true (redis/del "foo")))
(is (= nil (redis/get "foo"))))
(deftest type
(is (= :none (redis/type "nonexistent")))
(is (= :string (redis/type "foo")))
(is (= :list (redis/type "list")))
(is (= :set (redis/type "set"))))
(deftest keys
(is (= nil (redis/keys "a*")))
(is (= ["foo"] (redis/keys "f*")))
(is (= ["foo"] (redis/keys "f?o")))
(redis/set "fuu" "baz")
(is (= #{"foo" "fuu"} (clojure.core/set (redis/keys "f*")))))
(deftest randomkey
(redis/flushdb)
(redis/set "foo" "bar")
(is (= "foo" (redis/randomkey)))
(redis/flushdb)
(is (= "" (redis/randomkey))))
(deftest rename
(is (thrown? Exception (redis/rename "foo" "foo")))
(is (thrown? Exception (redis/rename "nonexistent" "foo")))
(redis/rename "foo" "bar")
(is (= "bar" (redis/get "bar")))
(is (= nil (redis/get "foo")))
(redis/set "foo" "bar")
(redis/set "bar" "baz")
(redis/rename "foo" "bar")
(is (= "bar" (redis/get "bar")))
(is (= nil (redis/get "foo")))
)
(deftest renamenx
(is (thrown? Exception (redis/renamenx "foo" "foo")))
(is (thrown? Exception (redis/renamenx "nonexistent" "foo")))
(is (= true (redis/renamenx "foo" "bar")))
(is (= "bar" (redis/get "bar")))
(is (= nil (redis/get "foo")))
(redis/set "foo" "bar")
(redis/set "bar" "baz")
(is (= false (redis/renamenx "foo" "bar")))
)
(deftest dbsize
(let [size-before (redis/dbsize)]
(redis/set "anewkey" "value")
(let [size-after (redis/dbsize)]
(is (= size-after
(+ 1 size-before))))))
(deftest expire
(is (= true (redis/expire "foo" 1)))
(Thread/sleep 2000)
(is (= false (redis/exists "foo")))
(redis/set "foo" "bar")
(is (= true (redis/expire "foo" 20)))
(is (= false (redis/expire "foo" 10)))
(is (= false (redis/expire "nonexistent" 42)))
)
(deftest ttl
(is (= -1 (redis/ttl "nonexistent")))
(is (= -1 (redis/ttl "foo")))
(redis/expire "foo" 42)
(is (< 40 (redis/ttl "foo"))))
;;
;; List commands
;;
(deftest rpush
(is (thrown? Exception (redis/rpush "foo")))
(redis/rpush "newlist" "one")
(is (= 1 (redis/llen "newlist")))
(is (= "one" (redis/lindex "newlist" 0)))
(redis/del "newlist")
(redis/rpush "list" "item")
(is (= "item" (redis/rpop "list"))))
(deftest lpush
(is (thrown? Exception (redis/lpush "foo")))
(redis/lpush "newlist" "item")
(is (= 1 (redis/llen "newlist")))
(is (= "item" (redis/lindex "newlist" 0)))
(redis/lpush "list" "item")
(is (= "item" (redis/lpop "list"))))
(deftest llen
(is (thrown? Exception (redis/llen "foo")))
(is (= 0 (redis/llen "newlist")))
(is (= 3 (redis/llen "list"))))
(deftest lrange
(is (thrown? Exception (redis/lrange "foo" 0 1)))
(is (= nil (redis/lrange "newlist" 0 42)))
(is (= ["one"] (redis/lrange "list" 0 0)))
(is (= ["three"] (redis/lrange "list" -1 -1)))
(is (= ["one" "two"] (redis/lrange "list" 0 1)))
(is (= ["one" "two" "three"] (redis/lrange "list" 0 2)))
(is (= ["one" "two" "three"] (redis/lrange "list" 0 42)))
(is (= [] (redis/lrange "list" 42 0)))
)
;; TBD
(deftest ltrim
(is (thrown? Exception (redis/ltrim "foo" 0 0))))
(deftest lindex
(is (thrown? Exception (redis/lindex "foo" 0)))
(is (= nil (redis/lindex "list" 42)))
(is (= nil (redis/lindex "list" -4)))
(is (= "one" (redis/lindex "list" 0)))
(is (= "three" (redis/lindex "list" 2)))
(is (= "three" (redis/lindex "list" -1))))
(deftest lset
(is (thrown? Exception (redis/lset "foo" 0 "bar")))
(is (thrown? Exception (redis/lset "list" 42 "value")))
(redis/lset "list" 0 "test")
(is (= "test" (redis/lindex "list" 0)))
(redis/lset "list" 2 "test2")
(is (= "test2" (redis/lindex "list" 2)))
(redis/lset "list" -1 "test3")
(is (= "test3" (redis/lindex "list" 2))))
;; TBD
(deftest lrem
(is (thrown? Exception (redis/lrem "foo" 0 "bar")))
(is (= 0 (redis/lrem "list" 0 ""))))
(deftest lpop
(is (thrown? Exception (redis/lpop "foo")))
(is (= "one" (redis/lpop "list"))))
(deftest rpop
(is (thrown? Exception (redis/rpop "foo")))
(is (= "three" (redis/rpop "list"))))
;;
;; Set commands
;;
(deftest sadd
(is (thrown? Exception (redis/sadd "foo" "bar")))
(is (= true (redis/sadd "newset" "member")))
(is (= true (redis/sismember "newset" "member")))
(is (= false (redis/sadd "set" "two")))
(is (= true (redis/sadd "set" "four")))
(is (= true (redis/sismember "set" "four"))))
(deftest srem
(is (thrown? Exception (redis/srem "foo" "bar")))
(is (thrown? Exception (redis/srem "newset" "member")))
(is (= true (redis/srem "set" "two")))
(is (= false (redis/sismember "set" "two")))
(is (= false (redis/srem "set" "blahonga"))))
(deftest smove
(is (thrown? Exception (redis/smove "foo" "set" "one")))
(is (thrown? Exception (redis/smove "set" "foo" "one")))
(redis/sadd "set1" "two")
(is (= false (redis/smove "set" "set1" "four")))
(is (= #{"two"} (redis/smembers "set1")))
(is (= true (redis/smove "set" "set1" "one")))
(is (= #{"one" "two"} (redis/smembers "set1"))))
(deftest scard
(is (thrown? Exception (redis/scard "foo")))
(is (= 3 (redis/scard "set"))))
(deftest sismember
(is (thrown? Exception (redis/sismember "foo" "bar")))
(is (= false (redis/sismember "set" "blahonga")))
(is (= true (redis/sismember "set" "two"))))
(deftest sinter
(is (thrown? Exception (redis/sinter "foo" "set")))
(is (= #{} (redis/sinter "nonexistent")))
(redis/sadd "set1" "one")
(redis/sadd "set1" "four")
(is (= #{"one" "two" "three"} (redis/sinter "set")))
(is (= #{"one"} (redis/sinter "set" "set1")))
(is (= #{} (redis/sinter "set" "set1" "nonexistent"))))
(deftest sinterstore
(redis/sinterstore "foo" "set")
(is (= #{"one" "two" "three"} (redis/smembers "foo")))
(redis/sadd "set1" "one")
(redis/sadd "set1" "four")
(redis/sinterstore "newset" "set" "set1")
(is (= #{"one"} (redis/smembers "newset"))))
(deftest sunion
(is (thrown? Exception (redis/sunion "foo" "set")))
(is (= #{} (redis/sunion "nonexistent")))
(redis/sadd "set1" "one")
(redis/sadd "set1" "four")
(is (= #{"one" "two" "three"} (redis/sunion "set")))
(is (= #{"one" "two" "three" "four"} (redis/sunion "set" "set1")))
(is (= #{"one" "two" "three" "four"} (redis/sunion "set" "set1" "nonexistent"))))
(deftest sunionstore
(redis/sunionstore "foo" "set")
(is (= #{"one" "two" "three"} (redis/smembers "foo")))
(redis/sadd "set1" "one")
(redis/sadd "set1" "four")
(redis/sunionstore "newset" "set" "set1")
(is (= #{"one" "two" "three" "four"} (redis/smembers "newset"))))
(deftest sdiff
(is (thrown? Exception (redis/sdiff "foo" "set")))
(is (= #{} (redis/sdiff "nonexistent")))
(redis/sadd "set1" "one")
(redis/sadd "set1" "four")
(is (= #{"one" "two" "three"} (redis/sdiff "set")))
(is (= #{"two" "three"} (redis/sdiff "set" "set1")))
(is (= #{"two" "three"} (redis/sdiff "set" "set1" "nonexistent"))))
(deftest sdiffstore
(redis/sdiffstore "foo" "set")
(is (= #{"one" "two" "three"} (redis/smembers "foo")))
(redis/sadd "set1" "one")
(redis/sadd "set1" "four")
(redis/sdiffstore "newset" "set" "set1")
(is (= #{"two" "three"} (redis/smembers "newset"))))
(deftest smembers
(is (thrown? Exception (redis/smembers "foo")))
(is (= #{"one" "two" "three"} (redis/smembers "set"))))
;;
;; Sorting
;;
(deftest sort
(redis/lpush "ids" 1)
(redis/lpush "ids" 4)
(redis/lpush "ids" 2)
(redis/lpush "ids" 3)
(redis/set "object_1" "one")
(redis/set "object_2" "two")
(redis/set "object_3" "three")
(redis/set "object_4" "four")
(redis/set "name_1" "Derek")
(redis/set "name_2" "Charlie")
(redis/set "name_3" "Bob")
(redis/set "name_4" "Alice")
(is (= ["one" "two" "three"]
(redis/sort "list")))
(is (= ["one" "three" "two"]
(redis/sort "list" :alpha)))
(is (= ["1" "2" "3" "4"]
(redis/sort "ids")))
(is (= ["1" "2" "3" "4"]
(redis/sort "ids" :asc)))
(is (= ["4" "3" "2" "1"]
(redis/sort "ids" :desc)))
(is (= ["1" "2"]
(redis/sort "ids" :asc :limit 0 2)))
(is (= ["4" "3"]
(redis/sort "ids" :desc :limit 0 2)))
(is (= ["4" "3" "2" "1"]
(redis/sort "ids" :by "name_*" :alpha)))
(is (= ["one" "two" "three" "four"]
(redis/sort "ids" :get "object_*")))
(is (= ["one" "two"]
(redis/sort "ids" :by "name_*" :alpha :limit 0 2 :desc :get "object_*"))))
;;
;; Multiple database handling commands
;;
(deftest select
(redis/select 0)
(is (= nil (redis/get "akeythat_probably_doesnotexsistindb0"))))
(deftest flushdb
(redis/flushdb)
(is (= 0 (redis/dbsize))))
;;
;; Persistence commands
;;
(deftest save
(redis/save))
(deftest bgsave
(redis/bgsave))
(deftest lastsave
(let [ages-ago (new java.util.Date (long 1))]
(is (.before ages-ago (redis/lastsave)))))

View File

@ -0,0 +1,156 @@
(ns redis.tests.internal
(:require [redis.internal :as redis])
(:use [clojure.contrib.test-is])
(:import [java.io StringReader BufferedReader]))
;;
;; Helpers
;;
(defn- wrap-in-reader
[#^String s]
(let [reader (BufferedReader. (StringReader. s))]
reader))
(defn- read-reply
[#^String s]
(redis/read-reply (wrap-in-reader s)))
;;
;; Command generation
;;
(deftest inline-command
(is (= "FOO\r\n"
(redis/inline-command "FOO")))
(is (= "FOO bar\r\n"
(redis/inline-command "FOO" "bar")))
(is (= "FOO bar baz\r\n"
(redis/inline-command "FOO" "bar" "baz"))))
(deftest bulk-command
(is (= "FOO 3\r\nbar\r\n"
(redis/bulk-command "FOO" "bar")))
(is (= "SET foo 3\r\nbar\r\n"
(redis/bulk-command "SET" "foo" "bar")))
(is (= "SET foo bar 3\r\nbaz\r\n"
(redis/bulk-command "SET" "foo" "bar" "baz"))))
(deftest sort-command
(is (= "SORT key\r\n"
(redis/sort-command "SORT" "key")))
(is (= "SORT key BY pattern\r\n"
(redis/sort-command "SORT" "key" :by "pattern")))
(is (= "SORT key LIMIT 0 10\r\n"
(redis/sort-command "SORT" "key" :limit 0 10)))
(is (= "SORT key ASC\r\n"
(redis/sort-command "SORT" "key" :asc)))
(is (= "SORT key DESC\r\n"
(redis/sort-command "SORT" "key" :desc)))
(is (= "SORT key ALPHA\r\n"
(redis/sort-command "SORT" "key" :alpha)))
(is (= "SORT key GET object_* GET object2_*\r\n"
(redis/sort-command "SORT" "key" :get "object_*" :get "object2_*")))
(is (= "SORT key BY weight_* LIMIT 0 10 GET object_* ALPHA DESC\r\n"
(redis/sort-command "SORT" "key"
:by "weight_*"
:limit 0 10
:get "object_*"
:alpha
:desc))))
;;
;; Reply parsing
;;
(deftest read-crlf
(is (thrown? Exception
(redis/read-crlf (wrap-in-reader "\n"))))
(is (thrown? Exception
(redis/read-crlf (wrap-in-reader ""))))
(is (thrown? Exception
(redis/read-crlf (wrap-in-reader "\r1"))))
(is (= nil
(redis/read-crlf (wrap-in-reader "\r\n")))))
;; (deftest read-newline-crlf
;; (is (thrown? Exception
;; (redis/read-line-crlf (wrap-in-reader "")))))
;;
;; Reply parsing
;;
(deftest reply
(is (thrown? Exception
(read-reply "")))
(is (thrown? Exception
(read-reply "\r\n"))))
(deftest error-reply
(is (thrown?
Exception
(read-reply "-\r\n")))
(is (thrown-with-msg?
Exception #".*Test"
(read-reply "-Test\r\n"))))
(deftest simple-reply
(is (thrown? Exception
(read-reply "+")))
(is (= ""
(read-reply "+\r\n")))
(is (= "foobar"
(read-reply "+foobar\r\n"))))
(deftest integer-reply
(is (thrown? Exception
(read-reply ":\r\n")))
(is (= 0
(read-reply ":0\r\n")))
(is (= 42
(read-reply ":42\r\n")))
(is (= 42
(read-reply ": 42 \r\n")))
(is (= 429348754
(read-reply ":429348754\r\n"))))
(deftest bulk-reply
(is (thrown? Exception
(read-reply "$\r\n")))
(is (thrown? Exception
(read-reply "$2\r\n1\r\n")))
(is (thrown? Exception
(read-reply "$3\r\n1\r\n")))
(is (= nil
(read-reply "$-1\r\n")))
(is (= "foobar"
(read-reply "$6\r\nfoobar\r\n")))
(is (= "foo\r\nbar"
(read-reply "$8\r\nfoo\r\nbar\r\n"))))
(deftest multi-bulk-reply
(is (thrown? Exception
(read-reply "*1\r\n")))
(is (thrown? Exception
(read-reply "*4\r\n:0\r\n:0\r\n:0\r\n")))
(is (= nil
(read-reply "*-1\r\n")))
(is (= [1]
(read-reply "*1\r\n:1\r\n")))
(is (= ["foo" "bar"]
(read-reply "*2\r\n+foo\r\n+bar\r\n")))
(is (= [1 "foo" "foo\r\nbar"]
(read-reply "*3\r\n:1\r\n+foo\r\n$8\r\nfoo\r\nbar\r\n"))))