mirror of
https://github.com/fluencelabs/redis
synced 2025-06-19 20:21:21 +00:00
Clojure library thanks to Ragnar Dahlén
This commit is contained in:
127
client-libraries/clojure/src/redis.clj
Normal file
127
client-libraries/clojure/src/redis.clj
Normal 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))
|
||||
)
|
263
client-libraries/clojure/src/redis/internal.clj
Normal file
263
client-libraries/clojure/src/redis/internal.clj
Normal 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)))
|
||||
|
||||
|
||||
|
387
client-libraries/clojure/src/redis/tests.clj
Normal file
387
client-libraries/clojure/src/redis/tests.clj
Normal 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)))))
|
||||
|
156
client-libraries/clojure/src/redis/tests/internal.clj
Normal file
156
client-libraries/clojure/src/redis/tests/internal.clj
Normal 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"))))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user