client libs removed from Redis git

This commit is contained in:
antirez 2009-11-03 11:25:32 +01:00
parent 5762b7f0f8
commit 1259672feb
110 changed files with 0 additions and 10740 deletions

View File

@ -1,38 +0,0 @@
Redis client libraries
----------------------
In this directory you'll find client libraries for different languages.
This are the latest releases available at the time this Redis tar.gz for this
release was created, and are good for most uses, but if you need more fresh
code or recent bugfixes read more.
How to get the lastest versions of client libraries source code
---------------------------------------------------------------
Note that while the pure PHP, Tcl, Python and Ruby_2 (Ruby alternative lib)
libraries are the most uptodate available libraries, all the other libraries
have their own repositories where it's possible to grab the most recent version:
Ruby lib source code:
http://github.com/ezmobius/redis-rb/tree/master
git://github.com/ezmobius/redis-rb.git
Erlang lib source code:
http://bitbucket.org/adroll/erldis/
Perl lib source code:
(web) http://svn.rot13.org/index.cgi/Redis
(svn) svn://svn.rot13.org/Redis/
Redis-php PHP C module:
http://code.google.com/p/phpredis/
Lua lib source code:
http://github.com/nrk/redis-lua/tree/master
git://github.com/nrk/redis-lua.git
Clojure lib source code:
http://github.com/ragnard/redis-clojure/
git://github.com/ragnard/redis-clojure.git
For all the rest check the Redis tarball or Git repository.

View File

@ -1,5 +0,0 @@
classes
\#*
.\#*
*.jar
build.properties

View File

@ -1,22 +0,0 @@
Copyright (c) 2009 Ragnar Dahlén (r.dahlen@gmail.com)
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,49 +0,0 @@
# redis-clojure
A Clojure client library for the
[Redis](http://code.google.com/p/redis) key value storage system.
## Dependencies
To use redis-clojure, you'll need:
* The [Clojure](http://clojure.org) programming language
* The [Clojure-Contrib](http://code.google.com/p/clojure-contrib) library (for running the tests)
## Building
To build redis-clojure:
ant -Dclojure.jar=/path/to/clojure.jar
This will build `redis-clojure.jar`.
## Running tests
To run tests:
ant -Dclojure.jar=/path/to/clojure.jar -Dclojure-contrib.jar=/path/to/clojure-contrib.jar test
*Note* you need to have `redis-server` running first.
## Using
To use redis-clojure in your application, simply make sure either
`redis-clojure.jar` or the contents of the `src/` directory is on your
classpath.
This can be accomplished like so:
(add-classpath "file:///path/to/redis-clojure.jar")
## Examples
Check the `examples/` directory.
*Note* you need to have `redis-server` running first.
## Todo
* Work on performance
* Maybe implement pipelining

View File

@ -1,179 +0,0 @@
(add-classpath "file:///Users/ragge/Projects/clojure/redis-clojure/redis-clojure.jar")
(ns benchmarks.clojure
(:use clojure.contrib.pprint)
(:require redis))
(defstruct benchmark-options
:host
:port
:db
:clients
:requests
:key-size
:keyspace-size
:data-size)
(defstruct client
:id
:request-times
:requests-performed
:requests-per-second)
(defstruct result
:options
:clients
:total-time
:requests)
(defmacro defbenchmark [name & body]
(let [benchmark-name (symbol (str name "-benchmark"))]
`(def ~(with-meta benchmark-name {:benchmark true})
(fn ~benchmark-name
[client# options# result#]
(redis/with-server
{:host (options# :host)
:port (options# :port)
:db (options# :db)}
(let [requests# (:requests options#)
requests-done# (:requests result#)]
(loop [requests-performed# 0 request-times# []]
(if (>= @requests-done# requests#)
(assoc client#
:request-times request-times#
:requests-performed requests-performed#)
(do
(let [start# (System/nanoTime)]
~@body
(let [end# (System/nanoTime)
elapsed# (/ (float (- end# start#)) 1000000.0)]
(dosync
(commute requests-done# inc))
(recur (inc requests-performed#)
(conj request-times# elapsed#)))))))))))))
(defbenchmark ping
(redis/ping))
(defbenchmark get
(redis/get (str "key-" (rand-int 1000))))
(defbenchmark set
(redis/set (str "key-" (rand-int 1000)) "abc"))
(defbenchmark exists-set-and-get
(let [key (str "key-" (rand-int 100))]
(redis/exists key)
(redis/set key "blahongaa!")
(redis/get key)))
(def *default-options* (struct-map benchmark-options
:host "127.0.0.1"
:port 6379
:db 15
:clients 1
:requests 10000))
(defn create-clients [options]
(for [id (range (:clients options))]
(agent (struct client id))))
(defn create-result [options clients]
(let [result (struct result options clients 0 (ref 0))]
result))
(defn requests-by-ms [clients]
(let [all-times (apply concat (map #(:request-times (deref %)) clients))
all-times-in-ms (map #(int (/ % 1)) all-times)]
(sort
(reduce
(fn [m time]
(if (m time)
(assoc m time (inc (m time)))
(assoc m time 1)))
{} all-times-in-ms))))
(defn report-request-times [clients requests]
(let [requests-dist (map #(let [perc (* 100 (/ (last %) requests))]
(conj % perc)) (requests-by-ms clients))]
(loop [items requests-dist
seen 0]
(if-not (empty? items)
(do
(let [item (first items)
seen (+ seen (last item))]
(println (format "%.2f%% < %d ms" (float seen) (inc (first item))))
(recur (rest items) seen)))))))
(defn report-client-rps [client]
(let [{:keys [id requests-performed request-times]} @client]
(when (< 0 requests-performed)
(let [total-time (apply + request-times)
requests-per-second (/ (float requests-performed)
total-time)]
(println total-time)
(println (format "Client %d: %f rps" id (float requests-per-second)))))))
(defn report-result [result]
(let [{:keys [clients options]} result
name (:name result)
time (:total-time result)
time-in-seconds (/ time 1000)
requests (deref (:requests result))
requests-per-second (/ requests time-in-seconds)
]
(do
(println (format "====== %s =====\n" name))
(println (format " %d requests completed in %f seconds\n" requests time-in-seconds))
(println (format " %d parallel clients\n" (:clients options)))
(report-request-times clients requests)
;(dorun (map report-client-rps clients))
(println (format "%f requests per second\n\n" requests-per-second))
)
)
)
(defn run-benchmark [fn options]
(let [clients (create-clients options)
result (create-result options clients)
start (System/nanoTime)]
(dorun
(map #(send-off % fn options result) clients))
(apply await clients)
(let [elapsed (/ (double (- (System/nanoTime) start)) 1000000.0)]
(dorun
(map #(when (agent-errors %)
(pprint (agent-errors %))) clients))
(assoc result
:name (str fn)
:options options
:clients clients
:total-time elapsed))))
(defn find-all-benchmarks [ns]
(filter #(:benchmark (meta %))
(vals (ns-map ns))))
(defn run-and-report [fn options]
(let [result (run-benchmark fn options)]
(report-result result)))
(defn run-all-benchmarks [ns]
(let [benchmarks (find-all-benchmarks ns)]
(dorun
(map #(run-and-report % *default-options*) benchmarks))))
;(run-all-benchmarks)
;(report-result (run-benchmark ping-benchmark *default-options*))
;(run-benchmark get-benchmark *default-options*)

View File

@ -1,25 +0,0 @@
(ns benchmarks.ruby
(:require redis))
(dotimes [n 4]
(redis/with-server
{:db 15}
(redis/set "foo" "The first line we sent to the server is some text")
(time
(dotimes [i 20000]
(let [key (str "key" i)]
(redis/set key "The first line we sent to the server is some text")
(redis/get "foo"))))))
;(redis/with-server
; {}
; (redis/set "foo" "The first line we sent to the server is some text")
; (time
; (dotimes [i 20000]
; (let [key (str "push_trim" i)]
; (redis/lpush key i)
; (redis/ltrim key 0 30)))))

View File

@ -1,91 +0,0 @@
<project name="redis" default="jar">
<description>
Redis client library for Clojure.
</description>
<property file="build.properties"/>
<property name="dist.dir" location="dist"/>
<property name="build.dir" location="classes"/>
<property name="lib.dir" location="lib"/>
<property name="source.dir" location="src"/>
<property name="redis-clojure.jar" location="redis-clojure.jar"/>
<target name="clean" description="Remove generated files">
<delete file="redis-clojure.jar"/>
<delete dir="${build.dir}"/>
</target>
<target name="init" depends="clean">
<tstamp/>
<mkdir dir="${build.dir}"/>
</target>
<target name="compile" depends="init" description="Compile sources">
<java classname="clojure.lang.Compile">
<classpath>
<path location="${build.dir}"/>
<path location="${source.dir}"/>
<path location="${clojure.jar}"/>
<path location="${clojure-contrib.jar}"/>
</classpath>
<sysproperty key="clojure.compile.path" value="${build.dir}"/>
<arg value="redis" />
</java>
</target>
<target name="jar" description="Create jar file" depends="compile">
<jar jarfile="${redis-clojure.jar}">
<path location="LICENSE"/>
<!--<fileset dir="${source.dir}" includes="**/*.clj"/>-->
<fileset dir="${build.dir}" includes="**/*.class"/>
<manifest>
<attribute name="Built-By" value="${user.name}"/>
</manifest>
</jar>
</target>
<target name="test" description="Run tests">
<java classname="clojure.main">
<classpath>
<path location="${source.dir}"/>
<path location="${clojure.jar}"/>
<path location="${clojure-contrib.jar}"/>
</classpath>
<arg value="-e" />
<arg value="(require 'redis.tests 'redis.tests.internal) (clojure.contrib.test-is/run-tests 'redis.tests 'redis.tests.internal)" />
</java>
</target>
<target name="bm" depends="benchmark"/>
<target name="benchmark" description="Run benchmark">
<java classname="clojure.main">
<classpath>
<path location="${basedir}"/>
<path location="${source.dir}"/>
<!--<path location="${redis-clojure.jar}"/>-->
<path location="${clojure.jar}"/>
<path location="${clojure-contrib.jar}"/>
</classpath>
<arg value="-e" />
<arg value="(require 'benchmarks.clojure) (benchmarks.clojure/run-all-benchmarks 'benchmarks.clojure)" />
</java>
</target>
<target name="benchmark-ruby" description="Run benchmark equivalent to the benchmarks of the Ruby library">
<java classname="clojure.main">
<classpath>
<path location="${basedir}"/>
<path location="${source.dir}"/>
<!--<path location="${redis-clojure.jar}"/>-->
<path location="${clojure.jar}"/>
<path location="${clojure-contrib.jar}"/>
</classpath>
<arg value="-e" />
<arg value="(require 'benchmarks.ruby)" />
</java>
</target>
</project>

View File

@ -1,33 +0,0 @@
;;
;; Simple demo of redis-clojure functionality
;;
;; Make sure redis-clojure.jar or the contents of the src/ directory
;; is on the classpath.
;;
;; Either:
;; (add-classpath "file:///path/to/redis-clojure.jar"
;; or:
;; (add-classpath "file:///path/to/redis/src-dir/")
;;
(add-classpath "file:///Users/ragge/Projects/clojure/redis-clojure/redis-clojure.jar")
(ns demo
(:require redis))
(redis/with-server
{:host "127.0.0.1" :port 6379 :db 0}
(do
(println "Sending ping")
(println "Reply:" (redis/ping))
(println "Server info:")
(let [info (redis/info)]
(dorun
(map (fn [entry]
(println (str "- "(first entry) ": " (last entry)))) info)))
(println "Setting key 'foo' to 'bar'")
(println "Reply:" (redis/set "foo" "bar"))
(println "Getting value of key 'foo'")
(println "Reply:" (redis/get "foo"))))

View File

@ -1,129 +0,0 @@
;(add-classpath "file:///Users/ragge/Projects/clojure/redis-clojure/src/")
(ns redis
(:refer-clojure :exclude [get set type keys sort])
(:use redis.internal))
;(set! *warn-on-reflection* true)
(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)
(spop [key] :inline)
(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)
;; Remote control
(info [] :inline string-to-map)
;;(monitor [] :inline))
)

View File

@ -1,263 +0,0 @@
(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)
(.setKeepAlive 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]
(char (.read reader))))
(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

@ -1,397 +0,0 @@
(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))))
(deftest lrem
(is (thrown? Exception (redis/lrem "foo" 0 "bar")))
(is (= 0 (redis/lrem "newlist" 0 "")))
(is (= 1 (redis/lrem "list" 1 "two")))
(is (= 1 (redis/lrem "list" 42 "three")))
(is (= 1 (redis/llen "list"))))
(deftest lpop
(is (thrown? Exception (redis/lpop "foo")))
(is (= nil (redis/lpop "newlist")))
(is (= "one" (redis/lpop "list")))
(is (= 2 (redis/llen "list"))))
(deftest rpop
(is (thrown? Exception (redis/rpop "foo")))
(is (= nil (redis/rpop "newlist")))
(is (= "three" (redis/rpop "list")))
(is (= 2 (redis/llen "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 (= false (redis/srem "newset" "member")))
(is (= true (redis/srem "set" "two")))
(is (= false (redis/sismember "set" "two")))
(is (= false (redis/srem "set" "blahonga"))))
(deftest spop
(is (thrown? Exception (redis/spop "foo" "bar")))
(is (= nil (redis/spop "newset")))
(is (contains? #{"one" "two" "three"} (redis/spop "set"))))
(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

@ -1,151 +0,0 @@
(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"))))

View File

@ -1,44 +0,0 @@
# Redis C++ Client Library Makefile
#CFLAGS?= -pedantic -O2 -Wall -W -DNDEBUG
CFLAGS?= -pedantic -O0 -W -DDEBUG -g
CC = g++
CLIENTOBJS = anet.o redisclient.o
LIBNAME = libredisclient.a
TESTAPP = test_client
TESTAPPOBJS = test_client.o
TESTAPPLIBS = $(LIBNAME) -lstdc++
all: $(LIBNAME) $(TESTAPP)
$(LIBNAME): $(CLIENTOBJS)
ar rcs $(LIBNAME) $(CLIENTOBJS)
.c.o:
$(CC) -c $(CFLAGS) $<
.cpp.o:
$(CC) -c $(CFLAGS) $<
$(TESTAPP): $(LIBNAME) $(TESTAPPOBJS)
$(CC) -o $(TESTAPP) $(TESTAPPOBJS) $(TESTAPPLIBS)
test: $(TESTAPP)
@./test_client
check: test
clean:
rm -rf $(LIBNAME) *.o $(TESTAPP)
dep:
$(CC) -MM *.c *.cpp
log:
git log '--pretty=format:%ad %s' --date=short > Changelog
anet.o: anet.c fmacros.h anet.h
redisclient.o: redisclient.cpp redisclient.h anet.h

View File

@ -1,16 +0,0 @@
redis-cpp-client
================
* A C++ client for the Redis_ key-value database (which is hosted at github_).
* This client has no external dependencies other than g++ (no Boost for instance).
* It uses anet from antirez_ (redis' author), which is bundled.
* This client is licensed under the same license as redis.
* Tested on Linux and Mac OS X.
* This is a work in progress. I will update this README when the client is "done".
If I had to put a version number on it right now, I'd call it version 0.85
.. _Redis: http://code.google.com/p/redis/
.. _github: http://github.com/antirez/redis/tree/master
.. _antirez: https://github.com/antirez

View File

@ -1,22 +0,0 @@
general:
- check for complete support for 0.100 (compiles; existing tests pass)
command handlers:
- support DEL as vararg
- support MLLEN and MSCARD
- support SDIFF
- support SDIFFSTORE
unit tests:
- sort with limit
- sort lexicographically
- sort with pattern and weights
extras:
- benchmarking "test" app
- consistent hashing?
maybe/someday:
- make all string literals constants so they can be easily changed
- add conveniences that store a std::set in its entirety (same for std::list, std::vector)

View File

@ -1,270 +0,0 @@
/* anet.c -- Basic TCP socket stuff made a bit less boring
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "fmacros.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include "anet.h"
static void anetSetError(char *err, const char *fmt, ...)
{
va_list ap;
if (!err) return;
va_start(ap, fmt);
vsnprintf(err, ANET_ERR_LEN, fmt, ap);
va_end(ap);
}
int anetNonBlock(char *err, int fd)
{
int flags;
/* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) {
anetSetError(err, "fcntl(F_GETFL): %s\n", strerror(errno));
return ANET_ERR;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpNoDelay(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1)
{
anetSetError(err, "setsockopt TCP_NODELAY: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetSetSendBuffer(char *err, int fd, int buffsize)
{
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
{
anetSetError(err, "setsockopt SO_SNDBUF: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpKeepAlive(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
anetSetError(err, "setsockopt SO_KEEPALIVE: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetResolve(char *err, char *host, char *ipbuf)
{
struct sockaddr_in sa;
sa.sin_family = AF_INET;
if (inet_aton(host, &sa.sin_addr) == 0) {
struct hostent *he;
he = gethostbyname(host);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", host);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
}
strcpy(ipbuf,inet_ntoa(sa.sin_addr));
return ANET_OK;
}
#define ANET_CONNECT_NONE 0
#define ANET_CONNECT_NONBLOCK 1
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
{
int s, on = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "creating socket: %s\n", strerror(errno));
return ANET_ERR;
}
/* Make sure connection-intensive things like the redis benckmark
* will be able to close/open sockets a zillion of times */
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
if (inet_aton(addr, &sa.sin_addr) == 0) {
struct hostent *he;
he = gethostbyname(addr);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", addr);
close(s);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
}
if (flags & ANET_CONNECT_NONBLOCK) {
if (anetNonBlock(err,s) != ANET_OK)
return ANET_ERR;
}
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS &&
flags & ANET_CONNECT_NONBLOCK)
return s;
anetSetError(err, "connect: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
return s;
}
int anetTcpConnect(char *err, char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
}
int anetTcpNonBlockConnect(char *err, char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
}
/* Like read(2) but make sure 'count' is read before to return
* (unless error or EOF condition is encountered) */
int anetRead(int fd, char *buf, int count)
{
int nread, totlen = 0;
while(totlen != count) {
nread = read(fd,buf,count-totlen);
if (nread == 0) return totlen;
if (nread == -1) return -1;
totlen += nread;
buf += nread;
}
return totlen;
}
/* Like write(2) but make sure 'count' is read before to return
* (unless error is encountered) */
int anetWrite(int fd, char *buf, int count)
{
int nwritten, totlen = 0;
while(totlen != count) {
nwritten = write(fd,buf,count-totlen);
if (nwritten == 0) return totlen;
if (nwritten == -1) return -1;
totlen += nwritten;
buf += nwritten;
}
return totlen;
}
int anetTcpServer(char *err, int port, char *bindaddr)
{
int s, on = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "socket: %s\n", strerror(errno));
return ANET_ERR;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
memset(&sa,0,sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bindaddr) {
if (inet_aton(bindaddr, &sa.sin_addr) == 0) {
anetSetError(err, "Invalid bind address\n");
close(s);
return ANET_ERR;
}
}
if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
anetSetError(err, "bind: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
if (listen(s, 32) == -1) {
anetSetError(err, "listen: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
return s;
}
int anetAccept(char *err, int serversock, char *ip, int *port)
{
int fd;
struct sockaddr_in sa;
unsigned int saLen;
while(1) {
saLen = sizeof(sa);
fd = accept(serversock, (struct sockaddr*)&sa, &saLen);
if (fd == -1) {
if (errno == EINTR)
continue;
else {
anetSetError(err, "accept: %s\n", strerror(errno));
return ANET_ERR;
}
}
break;
}
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
if (port) *port = ntohs(sa.sin_port);
return fd;
}

View File

@ -1,49 +0,0 @@
/* anet.c -- Basic TCP socket stuff made a bit less boring
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ANET_H
#define ANET_H
#define ANET_OK 0
#define ANET_ERR -1
#define ANET_ERR_LEN 256
int anetTcpConnect(char *err, char *addr, int port);
int anetTcpNonBlockConnect(char *err, char *addr, int port);
int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf);
int anetTcpServer(char *err, int port, char *bindaddr);
int anetAccept(char *err, int serversock, char *ip, int *port);
int anetWrite(int fd, char *buf, int count);
int anetNonBlock(char *err, int fd);
int anetTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
#endif

View File

@ -1,7 +0,0 @@
#ifndef _REDIS_FMACRO_H
#define _REDIS_FMACRO_H
#define _BSD_SOURCE
#define _XOPEN_SOURCE
#endif

View File

@ -1,894 +0,0 @@
/* redisclient.cpp -- a C++ client library for redis.
*
* Copyright (c) 2009, Brian Hammond <brian at fictorial dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "redisclient.h"
#include "anet.h"
#include <sstream>
#ifndef NDEBUG
#include <algorithm>
#include <iostream>
#include <ctime>
#endif
#include <cstring>
#include <cassert>
#include <sys/errno.h>
#include <sys/socket.h>
using namespace std;
namespace
{
const string whitespace(" \f\n\r\t\v");
const string CRLF("\r\n");
// Modifies in-place.
inline string & rtrim(string & str, const string & ws = whitespace)
{
string::size_type pos = str.find_last_not_of(ws);
str.erase(pos + 1);
return str;
}
vector<string>::size_type split(const string & str, char delim, vector<string> & elems)
{
stringstream ss(str);
string item;
vector<string>::size_type n = 0;
while (getline(ss, item, delim))
{
elems.push_back(item);
++n;
}
return n;
}
inline void split_lines(const string & str, vector<string> & elems)
{
split(str, '\n', elems);
for (vector<string>::iterator it = elems.begin(); it != elems.end(); ++it)
rtrim(*it);
}
#ifndef NDEBUG
void output_proto_debug(const string & data, bool is_received = true)
{
string escaped_data(data);
size_t pos;
while ((pos = escaped_data.find("\n")) != string::npos)
escaped_data.replace(pos, 1, "\\n");
while ((pos = escaped_data.find("\r")) != string::npos)
escaped_data.replace(pos, 1, "\\r");
cerr
<< time(NULL) << ": "
<< (is_received ? "RECV '" : "SEND '")
<< escaped_data
<< "'"
<< endl;
}
#endif
class makecmd
{
public:
explicit makecmd(const string & initial, bool finalize = false)
{
buffer_ << initial;
if (!finalize)
buffer_ << " ";
}
template <typename T>
makecmd & operator<<(T const & datum)
{
buffer_ << datum;
return *this;
}
template <typename T>
makecmd & operator<<(const vector<T> & data)
{
size_t n = data.size();
for (size_t i = 0; i < n; ++i)
{
buffer_ << data[i];
if (i < n - 1)
buffer_ << " ";
}
return *this;
}
operator std::string ()
{
buffer_ << CRLF;
return buffer_.str();
}
private:
ostringstream buffer_;
};
// Reads N bytes from given blocking socket.
string read_n(int socket, ssize_t n)
{
char * buffer = new char[n + 1];
buffer[n] = '\0';
char * bp = buffer;
ssize_t bytes_read = 0;
while (bytes_read != n)
{
ssize_t bytes_received = 0;
do bytes_received = recv(socket, bp, n - (bp - buffer), 0);
while (bytes_received < 0 && errno == EINTR);
if (bytes_received == 0)
throw redis::connection_error("connection was closed");
bytes_read += bytes_received;
bp += bytes_received;
}
string str(buffer);
delete [] buffer;
return str;
}
// Reads a single line of character data from the given blocking socket.
// Returns the line that was read, not including EOL delimiter(s). Both LF
// ('\n') and CRLF ("\r\n") delimiters are supported. If there was an I/O
// error reading from the socket, connection_error is raised. If max_size
// bytes are read before finding an EOL delimiter, a blank string is
// returned.
string read_line(int socket, ssize_t max_size = 2048)
{
assert(socket > 0);
assert(max_size > 0);
ostringstream oss;
enum { buffer_size = 64 };
char buffer[buffer_size];
memset(buffer, 0, buffer_size);
ssize_t total_bytes_read = 0;
bool found_delimiter = false;
while (total_bytes_read < max_size && !found_delimiter)
{
// Peek at what's available.
ssize_t bytes_received = 0;
do bytes_received = recv(socket, buffer, buffer_size, MSG_PEEK);
while (bytes_received < 0 && errno == EINTR);
if (bytes_received == 0)
throw redis::connection_error("connection was closed");
// Some data is available; Length might be < buffer_size.
// Look for newline in whatever was read though.
char * eol = static_cast<char *>(memchr(buffer, '\n', bytes_received));
// If found, write data from the buffer to the output string.
// Else, write the entire buffer and continue reading more data.
ssize_t to_read = bytes_received;
if (eol)
{
to_read = eol - buffer + 1;
oss.write(buffer, to_read);
found_delimiter = true;
}
else
oss.write(buffer, bytes_received);
// Now read from the socket to remove the peeked data from the socket's
// read buffer. This will not block since we've peeked already and know
// there's data waiting. It might fail if we were interrupted however.
do bytes_received = recv(socket, buffer, to_read, 0);
while (bytes_received < 0 && errno == EINTR);
}
// Construct final line string. Remove trailing CRLF-based whitespace.
string line = oss.str();
return rtrim(line, CRLF);
}
template <typename T>
T value_from_string(const string & data)
{
T value;
istringstream iss(data);
iss >> value;
if (iss.fail())
throw redis::value_error("invalid number");
return value;
}
const string status_reply_ok("OK");
const string prefix_status_reply_error("-ERR ");
const char prefix_status_reply_value = '+';
const char prefix_single_bulk_reply = '$';
const char prefix_multi_bulk_reply = '*';
const char prefix_int_reply = ':';
const string server_info_key_version = "redis_version";
const string server_info_key_bgsave_in_progress = "bgsave_in_progress";
const string server_info_key_connected_clients = "connected_clients";
const string server_info_key_connected_slaves = "connected_slaves";
const string server_info_key_used_memory = "used_memory";
const string server_info_key_changes_since_last_save = "changes_since_last_save";
const string server_info_key_last_save_time = "last_save_time";
const string server_info_key_total_connections_received = "total_connections_received";
const string server_info_key_total_commands_processed = "total_commands_processed";
const string server_info_key_uptime_in_seconds = "uptime_in_seconds";
const string server_info_key_uptime_in_days = "uptime_in_days";
const string server_info_key_role = "role";
const string server_info_value_role_master = "master";
const string server_info_value_role_slave = "slave";
}
namespace redis
{
redis_error::redis_error(const string & err) : err_(err)
{
}
redis_error::operator std::string ()
{
return err_;
}
redis_error::operator const std::string () const
{
return err_;
}
connection_error::connection_error(const string & err) : redis_error(err)
{
}
protocol_error::protocol_error(const string & err) : redis_error(err)
{
}
key_error::key_error(const string & err) : redis_error(err)
{
}
value_error::value_error(const string & err) : redis_error(err)
{
}
client::string_type client::missing_value("**nonexistent-key**");
client::client(const string_type & host, unsigned int port)
{
char err[ANET_ERR_LEN];
socket_ = anetTcpConnect(err, const_cast<char*>(host.c_str()), port);
if (socket_ == ANET_ERR)
throw connection_error(err);
anetTcpNoDelay(NULL, socket_);
}
client::~client()
{
if (socket_ != ANET_ERR)
close(socket_);
}
void client::auth(const client::string_type & pass)
{
send_(makecmd("AUTH") << pass);
recv_ok_reply_();
}
void client::set(const client::string_type & key,
const client::string_type & value)
{
send_(makecmd("SET") << key << ' ' << value.size() << CRLF << value);
recv_ok_reply_();
}
client::string_type client::get(const client::string_type & key)
{
send_(makecmd("GET") << key);
return recv_bulk_reply_();
}
client::string_type client::getset(const client::string_type & key,
const client::string_type & value)
{
send_(makecmd("GETSET") << key << ' ' << value.size() << CRLF << value);
return recv_bulk_reply_();
}
void client::mget(const client::string_vector & keys, string_vector & out)
{
send_(makecmd("MGET") << keys);
recv_multi_bulk_reply_(out);
}
bool client::setnx(const client::string_type & key,
const client::string_type & value)
{
send_(makecmd("SETNX") << key << ' ' << value.size() << CRLF << value);
return recv_int_reply_() == 1;
}
client::int_type client::incr(const client::string_type & key)
{
send_(makecmd("INCR") << key);
return recv_int_reply_();
}
client::int_type client::incrby(const client::string_type & key,
client::int_type by)
{
send_(makecmd("INCRBY") << key << ' ' << by);
return recv_int_reply_();
}
client::int_type client::decr(const client::string_type & key)
{
send_(makecmd("DECR") << key);
return recv_int_reply_();
}
client::int_type client::decrby(const client::string_type & key,
client::int_type by)
{
send_(makecmd("DECRBY") << key << ' ' << by);
return recv_int_reply_();
}
bool client::exists(const client::string_type & key)
{
send_(makecmd("EXISTS") << key);
return recv_int_reply_() == 1;
}
void client::del(const client::string_type & key)
{
send_(makecmd("DEL") << key);
recv_int_ok_reply_();
}
client::datatype client::type(const client::string_type & key)
{
send_(makecmd("TYPE") << key);
string response = recv_single_line_reply_();
if (response == "none") return datatype_none;
if (response == "string") return datatype_string;
if (response == "list") return datatype_list;
if (response == "set") return datatype_set;
return datatype_none;
}
client::int_type client::keys(const client::string_type & pattern,
client::string_vector & out)
{
send_(makecmd("KEYS") << pattern);
string resp = recv_bulk_reply_();
return split(resp, ' ', out);
}
client::string_type client::randomkey()
{
send_(makecmd("RANDOMKEY", true));
return recv_single_line_reply_();
}
void client::rename(const client::string_type & old_name,
const client::string_type & new_name)
{
send_(makecmd("RENAME") << old_name << ' ' << new_name);
recv_ok_reply_();
}
bool client::renamenx(const client::string_type & old_name,
const client::string_type & new_name)
{
send_(makecmd("RENAMENX") << old_name << ' ' << new_name);
return recv_int_reply_() == 1;
}
client::int_type client::dbsize()
{
send_(makecmd("DBSIZE"));
return recv_int_reply_();
}
void client::expire(const string_type & key, unsigned int secs)
{
send_(makecmd("EXPIRE") << key << ' ' << secs);
recv_int_ok_reply_();
}
void client::rpush(const client::string_type & key,
const client::string_type & value)
{
send_(makecmd("RPUSH") << key << ' ' << value.length() << CRLF << value);
recv_ok_reply_();
}
void client::lpush(const client::string_type & key,
const client::string_type & value)
{
send_(makecmd("LPUSH") << key << ' ' << value.length() << CRLF << value);
recv_ok_reply_();
}
client::int_type client::llen(const client::string_type & key)
{
send_(makecmd("LLEN") << key);
return recv_int_reply_();
}
client::int_type client::lrange(const client::string_type & key,
client::int_type start,
client::int_type end,
client::string_vector & out)
{
send_(makecmd("LRANGE") << key << ' ' << start << ' ' << end);
return recv_multi_bulk_reply_(out);
}
void client::ltrim(const client::string_type & key,
client::int_type start,
client::int_type end)
{
send_(makecmd("LTRIM") << key << ' ' << start << ' ' << end);
recv_ok_reply_();
}
client::string_type client::lindex(const client::string_type & key,
client::int_type index)
{
send_(makecmd("LINDEX") << key << ' ' << index);
return recv_bulk_reply_();
}
void client::lset(const client::string_type & key,
client::int_type index,
const client::string_type & value)
{
send_(makecmd("LSET") << key << ' ' << index << ' ' << value.length() << CRLF << value);
recv_ok_reply_();
}
client::int_type client::lrem(const client::string_type & key,
client::int_type count,
const client::string_type & value)
{
send_(makecmd("LREM") << key << ' ' << count << ' ' << value.length() << CRLF << value);
return recv_int_reply_();
}
client::string_type client::lpop(const client::string_type & key)
{
send_(makecmd("LPOP") << key);
return recv_bulk_reply_();
}
client::string_type client::rpop(const client::string_type & key)
{
send_(makecmd("RPOP") << key);
return recv_bulk_reply_();
}
void client::sadd(const client::string_type & key,
const client::string_type & value)
{
send_(makecmd("SADD") << key << ' ' << value.length() << CRLF << value);
recv_int_ok_reply_();
}
void client::srem(const client::string_type & key,
const client::string_type & value)
{
send_(makecmd("SREM") << key << ' ' << value.length() << CRLF << value);
recv_int_ok_reply_();
}
void client::smove(const client::string_type & srckey,
const client::string_type & dstkey,
const client::string_type & value)
{
send_(makecmd("SMOVE") << srckey << ' ' << dstkey << ' ' << value.length() << CRLF << value);
recv_int_ok_reply_();
}
client::int_type client::scard(const client::string_type & key)
{
send_(makecmd("SCARD") << key);
return recv_int_reply_();
}
bool client::sismember(const client::string_type & key,
const client::string_type & value)
{
send_(makecmd("SISMEMBER") << key << ' ' << value.length() << CRLF << value);
return recv_int_reply_() == 1;
}
client::int_type client::sinter(const client::string_vector & keys, client::string_set & out)
{
send_(makecmd("SINTER") << keys);
return recv_multi_bulk_reply_(out);
}
client::int_type client::sinterstore(const client::string_type & dstkey,
const client::string_vector & keys)
{
send_(makecmd("SINTERSTORE") << dstkey << ' ' << keys);
return recv_int_reply_();
}
client::int_type client::sunion(const client::string_vector & keys,
client::string_set & out)
{
send_(makecmd("SUNION") << keys);
return recv_multi_bulk_reply_(out);
}
client::int_type client::sunionstore(const client::string_type & dstkey,
const client::string_vector & keys)
{
send_(makecmd("SUNIONSTORE") << dstkey << ' ' << keys);
return recv_int_reply_();
}
client::int_type client::smembers(const client::string_type & key,
client::string_set & out)
{
send_(makecmd("SMEMBERS") << key);
return recv_multi_bulk_reply_(out);
}
void client::select(client::int_type dbindex)
{
send_(makecmd("SELECT") << dbindex);
recv_ok_reply_();
}
void client::move(const client::string_type & key,
client::int_type dbindex)
{
send_(makecmd("MOVE") << key << ' ' << dbindex);
recv_int_ok_reply_();
}
void client::flushdb()
{
send_(makecmd("FLUSHDB", true));
recv_ok_reply_();
}
void client::flushall()
{
send_(makecmd("FLUSHALL", true));
recv_ok_reply_();
}
client::int_type client::sort(const client::string_type & key,
client::string_vector & out,
client::sort_order order,
bool lexicographically)
{
send_(makecmd("SORT") << key
<< (order == sort_order_ascending ? " ASC" : " DESC")
<< (lexicographically ? " ALPHA" : ""));
return recv_multi_bulk_reply_(out);
}
client::int_type client::sort(const client::string_type & key,
client::string_vector & out,
client::int_type limit_start,
client::int_type limit_end,
client::sort_order order,
bool lexicographically)
{
send_(makecmd("SORT") << key
<< " LIMIT " << limit_start << ' ' << limit_end
<< (order == sort_order_ascending ? " ASC" : " DESC")
<< (lexicographically ? " ALPHA" : ""));
return recv_multi_bulk_reply_(out);
}
client::int_type client::sort(const client::string_type & key,
client::string_vector & out,
const client::string_type & by_pattern,
client::int_type limit_start,
client::int_type limit_end,
const client::string_vector & get_patterns,
client::sort_order order,
bool lexicographically)
{
makecmd m("SORT");
m << key
<< " BY " << by_pattern
<< " LIMIT " << limit_start << ' ' << limit_end;
client::string_vector::const_iterator it = get_patterns.begin();
for ( ; it != get_patterns.end(); ++it)
m << " GET " << *it;
m << (order == sort_order_ascending ? " ASC" : " DESC")
<< (lexicographically ? " ALPHA" : "");
send_(m);
return recv_multi_bulk_reply_(out);
}
void client::save()
{
send_(makecmd("SAVE", true));
recv_ok_reply_();
}
void client::bgsave()
{
send_(makecmd("BGSAVE", true));
recv_ok_reply_();
}
time_t client::lastsave()
{
send_(makecmd("LASTSAVE", true));
return recv_int_reply_();
}
void client::shutdown()
{
send_(makecmd("SHUTDOWN", true));
// we expected to get a connection_error as redis closes the connection on shutdown command.
try
{
recv_ok_reply_();
}
catch (connection_error & e)
{
}
}
void client::info(server_info & out)
{
send_(makecmd("INFO", true));
string response = recv_bulk_reply_();
if (response.empty())
throw protocol_error("empty");
string_vector lines;
split_lines(response, lines);
if (lines.empty())
throw protocol_error("empty line for info");
for (string_vector::const_iterator it = lines.begin();
it != lines.end(); ++it)
{
const string & line = *it;
string_vector line_parts;
split(line, ':', line_parts);
if (line_parts.size() != 2)
throw protocol_error("unexpected line format for info");
const string & key = line_parts[0];
const string & val = line_parts[1];
if (key == server_info_key_version)
out.version = val;
else if (key == server_info_key_bgsave_in_progress)
out.bgsave_in_progress = value_from_string<unsigned long>(val) == 1;
else if (key == server_info_key_connected_clients)
out.connected_clients = value_from_string<unsigned long>(val);
else if (key == server_info_key_connected_slaves)
out.connected_slaves = value_from_string<unsigned long>(val);
else if (key == server_info_key_used_memory)
out.used_memory = value_from_string<unsigned long>(val);
else if (key == server_info_key_changes_since_last_save)
out.changes_since_last_save = value_from_string<unsigned long>(val);
else if (key == server_info_key_last_save_time)
out.last_save_time = value_from_string<unsigned long>(val);
else if (key == server_info_key_total_connections_received)
out.total_connections_received = value_from_string<unsigned long>(val);
else if (key == server_info_key_total_commands_processed)
out.total_commands_processed = value_from_string<unsigned long>(val);
else if (key == server_info_key_uptime_in_seconds)
out.uptime_in_seconds = value_from_string<unsigned long>(val);
else if (key == server_info_key_uptime_in_days)
out.uptime_in_days = value_from_string<unsigned long>(val);
else if (key == server_info_key_role)
out.role = val == server_info_value_role_master ? role_master : role_slave;
else
throw protocol_error(string("unexpected info key '") + key + "'");
}
}
//
// Private methods
//
void client::send_(const string & msg)
{
#ifndef NDEBUG
output_proto_debug(msg, false);
#endif
if (anetWrite(socket_, const_cast<char *>(msg.data()), msg.size()) == -1)
throw connection_error(strerror(errno));
}
string client::recv_single_line_reply_()
{
string line = read_line(socket_);
#ifndef NDEBUG
output_proto_debug(line);
#endif
if (line.empty())
throw protocol_error("empty single line reply");
if (line.find(prefix_status_reply_error) == 0)
{
string error_msg = line.substr(prefix_status_reply_error.length());
if (error_msg.empty())
error_msg = "unknown error";
throw protocol_error(error_msg);
}
if (line[0] != prefix_status_reply_value)
throw protocol_error("unexpected prefix for status reply");
return line.substr(1);
}
void client::recv_ok_reply_()
{
if (recv_single_line_reply_() != status_reply_ok)
throw protocol_error("expected OK response");
}
client::int_type client::recv_bulk_reply_(char prefix)
{
string line = read_line(socket_);
#ifndef NDEBUG
output_proto_debug(line);
#endif
if (line[0] != prefix)
throw protocol_error("unexpected prefix for bulk reply");
return value_from_string<client::int_type>(line.substr(1));
}
string client::recv_bulk_reply_()
{
int_type length = recv_bulk_reply_(prefix_single_bulk_reply);
if (length == -1)
return client::missing_value;
int_type real_length = length + 2; // CRLF
string data = read_n(socket_, real_length);
#ifndef NDEBUG
output_proto_debug(data.substr(0, data.length()-2));
#endif
if (data.empty())
throw protocol_error("invalid bulk reply data; empty");
if (data.length() != static_cast<string::size_type>(real_length))
throw protocol_error("invalid bulk reply data; data of unexpected length");
data.erase(data.size() - 2);
return data;
}
client::int_type client::recv_multi_bulk_reply_(string_vector & out)
{
int_type length = recv_bulk_reply_(prefix_multi_bulk_reply);
if (length == -1)
throw key_error("no such key");
for (int_type i = 0; i < length; ++i)
out.push_back(recv_bulk_reply_());
return length;
}
client::int_type client::recv_multi_bulk_reply_(string_set & out)
{
int_type length = recv_bulk_reply_(prefix_multi_bulk_reply);
if (length == -1)
throw key_error("no such key");
for (int_type i = 0; i < length; ++i)
out.insert(recv_bulk_reply_());
return length;
}
client::int_type client::recv_int_reply_()
{
string line = read_line(socket_);
#ifndef NDEBUG
output_proto_debug(line);
#endif
if (line.empty())
throw protocol_error("invalid integer reply; empty");
if (line[0] != prefix_int_reply)
throw protocol_error("unexpected prefix for integer reply");
return value_from_string<client::int_type>(line.substr(1));
}
void client::recv_int_ok_reply_()
{
if (recv_int_reply_() != 1)
throw protocol_error("expecting int reply of 1");
}
}

View File

@ -1,488 +0,0 @@
/* redisclient.h -- a C++ client library for redis.
*
* Copyright (c) 2009, Brian Hammond <brian at fictorial dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef REDISCLIENT_H
#define REDISCLIENT_H
#include <string>
#include <vector>
#include <set>
#include <stdexcept>
#include <ctime>
namespace redis
{
enum server_role
{
role_master,
role_slave
};
struct server_info
{
std::string version;
bool bgsave_in_progress;
unsigned long connected_clients;
unsigned long connected_slaves;
unsigned long used_memory;
unsigned long changes_since_last_save;
unsigned long last_save_time;
unsigned long total_connections_received;
unsigned long total_commands_processed;
unsigned long uptime_in_seconds;
unsigned long uptime_in_days;
server_role role;
};
// Generic error that is thrown when communicating with the redis server.
class redis_error
{
public:
redis_error(const std::string & err);
operator std::string ();
operator const std::string () const;
private:
std::string err_;
};
// Some socket-level I/O or general connection error.
class connection_error : public redis_error
{
public:
connection_error(const std::string & err);
};
// Redis gave us a reply we were not expecting.
// Possibly an internal error (here or in redis, probably here).
class protocol_error : public redis_error
{
public:
protocol_error(const std::string & err);
};
// A key that you expected to exist does not in fact exist.
class key_error : public redis_error
{
public:
key_error(const std::string & err);
};
// A value of an expected type or other semantics was found to be invalid.
class value_error : public redis_error
{
public:
value_error(const std::string & err);
};
// You should construct a 'client' object per connection to a redis-server.
//
// Please read the online redis command reference:
// http://code.google.com/p/redis/wiki/CommandReference
//
// No provisions for customizing the allocator on the string/bulk value type
// (std::string) are provided. If needed, you can always change the
// string_type typedef in your local version.
class client
{
public:
typedef std::string string_type;
typedef std::vector<string_type> string_vector;
typedef std::set<string_type> string_set;
typedef long int_type;
explicit client(const string_type & host = "localhost",
unsigned int port = 6379);
~client();
//
// Connection handling
//
void auth(const string_type & pass);
//
// Commands operating on string values
//
// Note that empty string values do not denote nonexistent keys but well,
// empty values! If a nonexistent key is queried, the value returned will
// be missing_value, including when string_vector objects are returned.
//
static string_type missing_value;
// set a key to a string value
void set(const string_type & key, const string_type & value);
// return the string value of the key
string_type get(const string_type & key);
// set a key to a string returning the old value of the key
string_type getset(const string_type & key, const string_type & value);
// multi-get, return the strings values of the keys
void mget(const string_vector & keys, string_vector & out);
// set a key to a string value if the key does not exist. returns true if
// the key was set, else false. This does not throw since you are ok with
// this failing if the dst key already exists.
bool setnx(const string_type & key, const string_type & value);
// increment the integer value of key
// returns new value
int_type incr(const string_type & key);
// increment the integer value of key by integer
// returns new value
int_type incrby(const string_type & key, int_type by);
// decrement the integer value of key
// returns new value
int_type decr(const string_type & key);
// decrement the integer value of key by integer
// returns new value
int_type decrby(const string_type & key, int_type by);
// test if a key exists
bool exists(const string_type & key);
// delete a key
// throws if doesn't exist
void del(const string_type & key);
enum datatype
{
datatype_none, // key doesn't exist
datatype_string,
datatype_list,
datatype_set
};
// return the type of the value stored at key
datatype type(const string_type & key);
//
// Commands operating on the key space
//
// find all the keys matching a given pattern
// returns numbers of keys appended to 'out'
int_type keys(const string_type & pattern, string_vector & out);
// return a random key from the key space
// returns empty string if db is empty
string_type randomkey();
// rename the old key in the new one, destroying the new key if
// it already exists
void rename(const string_type & old_name, const string_type & new_name);
// rename the old key in the new one, if the new key does not already
// exist. This does not throw since you are ok with this failing if the
// new_name key already exists.
bool renamenx(const string_type & old_name, const string_type & new_name);
// return the number of keys in the current db
int_type dbsize();
// set a time to live in seconds on a key.
// fails if there's already a timeout on the key.
// NB: there's currently no generic way to remove a timeout on a key
void expire(const string_type & key, unsigned int secs);
//
// Commands operating on lists
//
// Append an element to the tail of the list value at key
void rpush(const string_type & key, const string_type & value);
// Append an element to the head of the list value at key
void lpush(const string_type & key, const string_type & value);
// Return the length of the list value at key
// Returns 0 if the list does not exist; see 'exists'
int_type llen(const string_type & key);
// Fetch a range of elements from the list at key
// end can be negative for reverse offsets
// Returns number of elements appended to 'out'
int_type lrange(const string_type & key,
int_type start,
int_type end,
string_vector & out);
// Fetches the entire list at key.
int_type get_list(const string_type & key, string_vector & out)
{
return lrange(key, 0, -1, out);
}
// Trim the list at key to the specified range of elements
void ltrim(const string_type & key, int_type start, int_type end);
// Return the element at index position from the list at key
string_type lindex(const string_type & key, int_type);
// set a new value as the element at index position of the list at key
void lset(const string_type & key,
int_type index,
const string_type &);
// If count is zero all the elements are removed. If count is negative
// elements are removed from tail to head, instead to go from head to tail
// that is the normal behaviour. So for example LREM with count -2 and
// hello as value to remove against the list (a,b,c,hello,x,hello,hello)
// will lave the list (a,b,c,hello,x). Returns the number of removed
// elements if the operation succeeded.
//
// Note: this will not throw if the number of elements removed != count
// since you might want to remove at most count elements by don't care if
// < count elements are removed. See lrem_exact().
int_type lrem(const string_type & key,
int_type count,
const string_type & value);
// An extension of 'lrem' that wants to remove exactly 'count' elements.
// Throws value_error if 'count' elements are not found & removed from the
// list at 'key'.
void lrem_exact(const string_type & key,
int_type count,
const string_type & value)
{
if (lrem(key, count, value) != count)
throw value_error("failed to remove exactly N elements from list");
}
// Return and remove (atomically) the first element of the list at key
string_type lpop(const string_type & key);
// Return and remove (atomically) the last element of the list at key
string_type rpop(const string_type & key);
//
// Commands operating on sets
//
// Add the specified member to the set value at key
// returns true if added, or false if already a member of the set.
void sadd(const string_type & key, const string_type & value);
// Remove the specified member from the set value at key
// returns true if removed or false if value is not a member of the set.
void srem(const string_type & key, const string_type & value);
// Move the specified member from one set to another atomically
// returns true if element was moved, else false (e.g. not found)
void smove(const string_type & srckey,
const string_type & dstkey,
const string_type & value);
// Return the number of elements (the cardinality) of the set at key
int_type scard(const string_type & key);
// Test if the specified value is a member of the set at key
// Returns false if key doesn't exist or value is not a member of the set at key
bool sismember(const string_type & key, const string_type & value);
// Return the intersection between the sets stored at key1, key2, ..., keyN
int_type sinter(const string_vector & keys, string_set & out);
// Compute the intersection between the sets stored at key1, key2, ...,
// keyN, and store the resulting set at dstkey
// Returns the number of items in the intersection
int_type sinterstore(const string_type & dstkey, const string_vector & keys);
// Return the union between the sets stored at key1, key2, ..., keyN
int_type sunion(const string_vector & keys, string_set & out);
// Compute the union between the sets stored at key1, key2, ..., keyN,
// and store the resulting set at dstkey
// Returns the number of items in the intersection
int_type sunionstore(const string_type & dstkey, const string_vector & keys);
// Return all the members of the set value at key
int_type smembers(const string_type & key, string_set & out);
//
// Multiple databases handling commands
//
// Select the DB having the specified index
void select(int_type dbindex);
// Move the key from the currently selected DB to the DB having as index
// dbindex. Throws if key was already in the db at dbindex or not found in
// currently selected db.
void move(const string_type & key, int_type dbindex);
// Remove all the keys of the currently selected DB
void flushdb();
// Remove all the keys from all the databases
void flushall();
//
// Sorting
// Just go read http://code.google.com/p/redis/wiki/SortCommand
//
enum sort_order
{
sort_order_ascending,
sort_order_descending
};
int_type sort(const string_type & key,
string_vector & out,
sort_order order = sort_order_ascending,
bool lexicographically = false);
int_type sort(const string_type & key,
string_vector & out,
int_type limit_start,
int_type limit_end,
sort_order order = sort_order_ascending,
bool lexicographically = false);
int_type sort(const string_type & key,
string_vector & out,
const string_type & by_pattern,
int_type limit_start,
int_type limit_end,
const string_vector & get_patterns,
sort_order order = sort_order_ascending,
bool lexicographically = false);
//
// Persistence control commands
//
// Synchronously save the DB on disk
void save();
// Asynchronously save the DB on disk
void bgsave();
// Return the UNIX time stamp of the last successfully saving of the
// dataset on disk
time_t lastsave();
// Synchronously save the DB on disk, then shutdown the server. This
// object's connection to the server will be lost on success. Otherwise,
// redis_error is raised. Thus, on success, you should delete or otherwise
// no longer use the object.
void shutdown();
//
// Remote server control commands
//
// Provide information and statistics about the server
void info(server_info & out);
private:
client(const client &);
client & operator=(const client &);
void send_(const std::string &);
void recv_ok_reply_();
void recv_int_ok_reply_();
std::string recv_single_line_reply_();
int_type recv_bulk_reply_(char prefix);
std::string recv_bulk_reply_();
int_type recv_multi_bulk_reply_(string_vector & out);
int_type recv_multi_bulk_reply_(string_set & out);
int_type recv_int_reply_();
private:
int socket_;
};
}
#endif

View File

@ -1,629 +0,0 @@
#include "redisclient.h"
#include <iostream>
using namespace std;
#define ASSERT_EQUAL(x,y) assert_equal(x, y, __LINE__)
#define ASSERT_NOT_EQUAL(x,y) assert_not_equal(x, y, __LINE__)
#define ASSERT_GT(x,y) assert_gt(x, y, __LINE__)
template <typename T>
void assert_equal(const T & actual, const T & expected, int lineno)
{
#ifndef NDEBUG
cerr << "assert_equal('" << expected << "', '" << actual << "')" << endl;
#endif
if (expected != actual)
{
cerr << "expected '" << expected << "' got '" << actual << "'" << endl
<< "failing test called from line " << lineno << endl;
exit(1);
}
#ifndef NDEBUG
cerr << "... OK" << endl;
#endif
}
template <typename T>
void assert_not_equal(const T & a, const T & b, int lineno)
{
if (a == b)
{
cerr << "expected inequality" << endl
<< "failing test called from line " << lineno << endl;
exit(1);
}
}
template <typename T>
void assert_gt(const T & a, const T & b, int lineno)
{
#ifndef NDEBUG
cerr << "assert_gt('" << a << "', '" << b << "')" << endl;
#endif
if (a <= b)
{
cerr << "expected '" << a << "' > '" << b << "'" << endl
<< "failing test called from line " << lineno << endl;
exit(1);
}
#ifndef NDEBUG
cerr << "... OK" << endl;
#endif
}
void test(const string & name)
{
#ifndef NDEBUG
cerr << "------------------------------" << endl
<< "starting test: " << name << endl;
#endif
}
int main(int argc, char ** argv)
{
try
{
redis::client c;
// Test on high number databases
c.select(14);
c.flushdb();
c.select(15);
c.flushdb();
string foo("foo"), bar("bar"), baz("baz"), buz("buz"), goo("goo");
test("auth");
{
// TODO ... needs a conf for redis-server
}
test("info");
{
// doesn't throw? then, has valid numbers and known info-keys.
redis::server_info info;
c.info(info);
}
test("set, get");
{
c.set(foo, bar);
ASSERT_EQUAL(c.get(foo), bar);
}
test("getset");
{
ASSERT_EQUAL(c.getset(foo, baz), bar);
ASSERT_EQUAL(c.get(foo), baz);
}
test("mget");
{
string x_val("hello"), y_val("world");
c.set("x", x_val);
c.set("y", y_val);
redis::client::string_vector keys;
keys.push_back("x");
keys.push_back("y");
redis::client::string_vector vals;
c.mget(keys, vals);
ASSERT_EQUAL(vals.size(), size_t(2));
ASSERT_EQUAL(vals[0], x_val);
ASSERT_EQUAL(vals[1], y_val);
}
test("setnx");
{
ASSERT_EQUAL(c.setnx(foo, bar), false);
ASSERT_EQUAL(c.setnx(buz, baz), true);
ASSERT_EQUAL(c.get(buz), baz);
}
test("incr");
{
ASSERT_EQUAL(c.incr("goo"), 1L);test("nonexistent (0) -> 1");
ASSERT_EQUAL(c.incr("goo"), 2L);test("1->2");
}
test("decr");
{
ASSERT_EQUAL(c.decr("goo"), 1L);test("2->1");
ASSERT_EQUAL(c.decr("goo"), 0L);test("1->0");
}
test("incrby");
{
ASSERT_EQUAL(c.incrby("goo", 3), 3L);test("0->3");
ASSERT_EQUAL(c.incrby("goo", 2), 5L);test("3->5");
}
test("exists");
{
ASSERT_EQUAL(c.exists("goo"), true);
}
test("del");
{
c.del("goo");
ASSERT_EQUAL(c.exists("goo"), false);
}
test("type (basic)");
{
ASSERT_EQUAL(c.type(goo), redis::client::datatype_none);test("we deleted it");
c.set(goo, "redis");
ASSERT_EQUAL(c.type(goo), redis::client::datatype_string);
}
test("keys");
{
redis::client::string_vector keys;
ASSERT_EQUAL(c.keys("*oo", keys), 2L);
ASSERT_EQUAL(keys.size(), 2UL);
ASSERT_EQUAL(keys[0], foo);
ASSERT_EQUAL(keys[1], goo);
}
test("randomkey");
{
ASSERT_GT(c.randomkey().size(), 0UL);
}
test("rename");
{
ASSERT_EQUAL(c.exists("foo"), true);
ASSERT_EQUAL(c.exists("doo"), false);
c.rename("foo", "doo");
ASSERT_EQUAL(c.exists("foo"), false);
ASSERT_EQUAL(c.exists("doo"), true);
}
test("renamenx");
{
ASSERT_EQUAL(c.exists("doo"), true);
ASSERT_EQUAL(c.exists("foo"), false);
ASSERT_EQUAL(c.renamenx("doo", "foo"), true);
ASSERT_EQUAL(c.exists("doo"), false);
ASSERT_EQUAL(c.exists("foo"), true);
ASSERT_EQUAL(c.renamenx("goo", "foo"), false);
ASSERT_EQUAL(c.exists("foo"), true);
ASSERT_EQUAL(c.exists("goo"), true);
}
test("dbsize");
{
ASSERT_GT(c.dbsize(), 0L);
}
test("expire");
{
c.expire("goo", 1);
#ifndef NDEBUG
cerr << "please wait a few seconds.." << endl;
#endif
sleep(2);
ASSERT_EQUAL(c.exists("goo"), false);
}
test("rpush");
{
ASSERT_EQUAL(c.exists("list1"), false);
c.rpush("list1", "val1");
ASSERT_EQUAL(c.llen("list1"), 1L);
ASSERT_EQUAL(c.type("list1"), redis::client::datatype_list);
c.rpush("list1", "val2");
ASSERT_EQUAL(c.llen("list1"), 2L);
ASSERT_EQUAL(c.lindex("list1", 0), string("val1"));
ASSERT_EQUAL(c.lindex("list1", 1), string("val2"));
}
test("lpush");
{
c.del("list1");
ASSERT_EQUAL(c.exists("list1"), false);
c.lpush("list1", "val1");
ASSERT_EQUAL(c.type("list1"), redis::client::datatype_list);
ASSERT_EQUAL(c.llen("list1"), 1L);
c.lpush("list1", "val2");
ASSERT_EQUAL(c.llen("list1"), 2L);
ASSERT_EQUAL(c.lindex("list1", 0), string("val2"));
ASSERT_EQUAL(c.lindex("list1", 1), string("val1"));
}
test("llen");
{
c.del("list1");
ASSERT_EQUAL(c.exists("list1"), false);
ASSERT_EQUAL(c.llen("list1"), 0L);
c.lpush("list1", "x");
ASSERT_EQUAL(c.llen("list1"), 1L);
c.lpush("list1", "y");
ASSERT_EQUAL(c.llen("list1"), 2L);
}
test("lrange");
{
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 2L);
redis::client::string_vector vals;
ASSERT_EQUAL(c.lrange("list1", 0, -1, vals), 2L);
ASSERT_EQUAL(vals.size(), 2UL);
ASSERT_EQUAL(vals[0], string("y"));
ASSERT_EQUAL(vals[1], string("x"));
}
test("lrange with subset of full list");
{
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 2L);
redis::client::string_vector vals;
ASSERT_EQUAL(c.lrange("list1", 0, 1, vals), 2L); // inclusive, so entire list
ASSERT_EQUAL(vals.size(), 2UL);
ASSERT_EQUAL(vals[0], string("y"));
ASSERT_EQUAL(vals[1], string("x"));
redis::client::string_vector vals2;
ASSERT_EQUAL(c.lrange("list1", 0, 0, vals2), 1L); // inclusive, so first item
ASSERT_EQUAL(vals2.size(), 1UL);
ASSERT_EQUAL(vals2[0], string("y"));
redis::client::string_vector vals3;
ASSERT_EQUAL(c.lrange("list1", -1, -1, vals3), 1L); // inclusive, so first item
ASSERT_EQUAL(vals3.size(), 1UL);
ASSERT_EQUAL(vals3[0], string("x"));
}
test("get_list");
{
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 2L);
redis::client::string_vector vals;
ASSERT_EQUAL(c.get_list("list1", vals), 2L);
ASSERT_EQUAL(vals.size(), 2UL);
ASSERT_EQUAL(vals[0], string("y"));
ASSERT_EQUAL(vals[1], string("x"));
}
test("ltrim");
{
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 2L);
c.ltrim("list1", 0, 0);
ASSERT_EQUAL(c.exists("list1"), true);
ASSERT_EQUAL(c.llen("list1"), 1L);
redis::client::string_vector vals;
ASSERT_EQUAL(c.get_list("list1", vals), 1L);
ASSERT_EQUAL(vals[0], string("y"));
}
test("lindex");
{
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
c.rpush("list1", "x");
ASSERT_EQUAL(c.llen("list1"), 2L);
ASSERT_EQUAL(c.lindex("list1", -1), string("x"));
ASSERT_EQUAL(c.lindex("list1", 1), string("x"));
}
test("lset");
{
c.lset("list1", 1, "z");
ASSERT_EQUAL(c.lindex("list1", 1), string("z"));
ASSERT_EQUAL(c.llen("list1"), 2L);
}
test("lrem");
{
c.lrem("list1", 1, "z");
ASSERT_EQUAL(c.llen("list1"), 1L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
// list1 = [ y ]
ASSERT_EQUAL(c.lrem("list1", 0, "q"), 0L);
c.rpush("list1", "z");
c.rpush("list1", "z");
c.rpush("list1", "z");
c.rpush("list1", "a");
// list1 = [ y, z, z, z, a ]
ASSERT_EQUAL(c.lrem("list1", 2, "z"), 2L);
// list1 = [ y, z, a ]
ASSERT_EQUAL(c.llen("list1"), 3L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
ASSERT_EQUAL(c.lindex("list1", 1), string("z"));
ASSERT_EQUAL(c.lindex("list1", 2), string("a"));
c.rpush("list1", "z");
// list1 = [ y, z, a, z ]
ASSERT_EQUAL(c.lrem("list1", -1, "z"), 1L); // <0 => rm R to L
// list1 = [ y, z, a ]
ASSERT_EQUAL(c.llen("list1"), 3L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
ASSERT_EQUAL(c.lindex("list1", 1), string("z"));
ASSERT_EQUAL(c.lindex("list1", 2), string("a"));
// list1 = [ y, z, a ]
// try to remove 5 'a's but there's only 1 ... no problem.
ASSERT_EQUAL(c.lrem("list1", 5, "a"), 1L);
// list1 = [ y, z ]
ASSERT_EQUAL(c.llen("list1"), 2L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
ASSERT_EQUAL(c.lindex("list1", 1), string("z"));
}
test("lrem_exact");
{
// list1 = [ y, z ]
// try to remove 5 'z's but there's only 1 ... now it's a problem.
bool threw = false;
try
{
c.lrem_exact("list1", 5, "z");
}
catch (redis::value_error & e)
{
threw = true;
}
ASSERT_EQUAL(threw, true);
// This DOES remove the one 'z' though
// list1 = [ y ]
ASSERT_EQUAL(c.llen("list1"), 1L);
ASSERT_EQUAL(c.lindex("list1", 0), string("y"));
}
test("lpop");
{
ASSERT_EQUAL(c.lpop("list1"), string("y"));
// list1 = []
ASSERT_EQUAL(c.lpop("list1"), redis::client::missing_value);
}
test("rpop");
{
c.rpush("list1", "hello");
c.rpush("list1", "world");
ASSERT_EQUAL(c.rpop("list1"), string("world"));
ASSERT_EQUAL(c.rpop("list1"), string("hello"));
ASSERT_EQUAL(c.lpop("list1"), redis::client::missing_value);
}
test("sadd");
{
c.sadd("set1", "sval1");
ASSERT_EQUAL(c.exists("set1"), true);
ASSERT_EQUAL(c.type("set1"), redis::client::datatype_set);
ASSERT_EQUAL(c.sismember("set1", "sval1"), true);
}
test("srem");
{
c.srem("set1", "sval1");
ASSERT_EQUAL(c.exists("set1"), true);
ASSERT_EQUAL(c.type("set1"), redis::client::datatype_set);
ASSERT_EQUAL(c.sismember("set1", "sval1"), false);
}
test("smove");
{
c.sadd("set1", "hi");
// set1 = { hi }
ASSERT_EQUAL(c.exists("set2"), false);
c.smove("set1", "set2", "hi");
ASSERT_EQUAL(c.sismember("set1", "hi"), false);
ASSERT_EQUAL(c.sismember("set2", "hi"), true);
}
test("scard");
{
ASSERT_EQUAL(c.scard("set1"), 0L);
ASSERT_EQUAL(c.scard("set2"), 1L);
}
test("sismember");
{
// see above
}
test("smembers");
{
c.sadd("set2", "bye");
redis::client::string_set members;
ASSERT_EQUAL(c.smembers("set2", members), 2L);
ASSERT_EQUAL(members.size(), 2UL);
ASSERT_NOT_EQUAL(members.find("hi"), members.end());
ASSERT_NOT_EQUAL(members.find("bye"), members.end());
}
test("sinter");
{
c.sadd("set3", "bye");
c.sadd("set3", "bye2");
redis::client::string_vector keys;
keys.push_back("set2");
keys.push_back("set3");
redis::client::string_set intersection;
ASSERT_EQUAL(c.sinter(keys, intersection), 1L);
ASSERT_EQUAL(intersection.size(), 1UL);
ASSERT_NOT_EQUAL(intersection.find("bye"), intersection.end());
}
test("sinterstore");
{
c.sadd("seta", "1");
c.sadd("seta", "2");
c.sadd("seta", "3");
c.sadd("setb", "2");
c.sadd("setb", "3");
c.sadd("setb", "4");
redis::client::string_vector keys;
keys.push_back("seta");
keys.push_back("setb");
ASSERT_EQUAL(c.sinterstore("setc", keys), 2L);
redis::client::string_set members;
ASSERT_EQUAL(c.smembers("setc", members), 2L);
ASSERT_EQUAL(members.size(), 2UL);
ASSERT_NOT_EQUAL(members.find("2"), members.end());
ASSERT_NOT_EQUAL(members.find("3"), members.end());
}
test("sunion");
{
c.sadd("setd", "1");
c.sadd("sete", "2");
redis::client::string_vector keys;
keys.push_back("setd");
keys.push_back("sete");
redis::client::string_set a_union;
ASSERT_EQUAL(c.sunion(keys, a_union), 2L);
ASSERT_EQUAL(a_union.size(), 2UL);
ASSERT_NOT_EQUAL(a_union.find("1"), a_union.end());
ASSERT_NOT_EQUAL(a_union.find("2"), a_union.end());
}
test("sunionstore");
{
c.sadd("setf", "1");
c.sadd("setg", "2");
redis::client::string_vector keys;
keys.push_back("setf");
keys.push_back("setg");
ASSERT_EQUAL(c.sunionstore("seth", keys), 2L);
redis::client::string_set members;
ASSERT_EQUAL(c.smembers("seth", members), 2L);
ASSERT_EQUAL(members.size(), 2UL);
ASSERT_NOT_EQUAL(members.find("1"), members.end());
ASSERT_NOT_EQUAL(members.find("2"), members.end());
}
test("move");
{
c.select(14);
ASSERT_EQUAL(c.exists("ttt"), false);
c.select(15);
c.set("ttt", "uuu");
c.move("ttt", 14);
c.select(14);
ASSERT_EQUAL(c.exists("ttt"), true);
c.select(15);
ASSERT_EQUAL(c.exists("ttt"), false);
}
test("move should fail since key exists already");
{
c.select(14);
c.set("ttt", "xxx");
c.select(15);
c.set("ttt", "uuu");
bool threw = false;
try
{
c.move("ttt", 14);
}
catch (redis::protocol_error & e)
{
threw = true;
}
ASSERT_EQUAL(threw, true);
c.select(14);
ASSERT_EQUAL(c.exists("ttt"), true);
c.select(15);
ASSERT_EQUAL(c.exists("ttt"), true);
}
test("sort ascending");
{
c.sadd("sort1", "3");
c.sadd("sort1", "2");
c.sadd("sort1", "1");
redis::client::string_vector sorted;
ASSERT_EQUAL(c.sort("sort1", sorted), 3L);
ASSERT_EQUAL(sorted.size(), 3UL);
ASSERT_EQUAL(sorted[0], string("1"));
ASSERT_EQUAL(sorted[1], string("2"));
ASSERT_EQUAL(sorted[2], string("3"));
}
test("sort descending");
{
redis::client::string_vector sorted;
ASSERT_EQUAL(c.sort("sort1", sorted, redis::client::sort_order_descending), 3L);
ASSERT_EQUAL(sorted.size(), 3UL);
ASSERT_EQUAL(sorted[0], string("3"));
ASSERT_EQUAL(sorted[1], string("2"));
ASSERT_EQUAL(sorted[2], string("1"));
}
test("sort with limit");
{
// TODO
}
test("sort lexicographically");
{
// TODO
}
test("sort with pattern and weights");
{
// TODO
}
test("save");
{
c.save();
}
test("bgsave");
{
c.bgsave();
}
test("lastsave");
{
ASSERT_GT(c.lastsave(), 0L);
}
test("shutdown");
{
// You can test this if you really want to ...
// c.shutdown();
}
}
catch (redis::redis_error & e)
{
cerr << "got exception: " << string(e) << endl << "FAIL" << endl;
return 1;
}
cout << endl << "testing completed successfully" << endl;
return 0;
}

View File

@ -1,2 +0,0 @@
repo: 9e1f35ed7fdc7b3da7f5ff66a71d1975b85e2ae5
node: 85e28ca5597e22ff1dde18ed4625f41923128993

View File

@ -1,2 +0,0 @@
syntax: glob
*.beam

View File

@ -1,22 +0,0 @@
Copyright (c) 2009
adroll.com
Valentino Volonghi
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,29 +0,0 @@
LIBDIR=`erl -eval 'io:format("~s~n", [code:lib_dir()])' -s init stop -noshell`
all:
mkdir -p ebin/
(cd src;$(MAKE))
(cd test;$(MAKE))
clean: clean_tests
(cd src;$(MAKE) clean)
rm -rf erl_crash.dump *.beam
clean_tests:
(cd test;$(MAKE) clean)
rm -rf erl_crash.dump *.beam
test: clean
mkdir -p ebin/
(cd src;$(MAKE))
(cd test;$(MAKE))
(cd test;$(MAKE) test)
testrun: all
mkdir -p ebin/
(cd test;$(MAKE) test)
install: all
mkdir -p ${LIBDIR}/erldis-0.0.1/{ebin,include}
for i in ebin/*.beam; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done
for i in include/*.hrl; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done

View File

@ -1 +0,0 @@
-record(redis, {socket,buffer=[],reply_caller,calls=0,remaining=0,pstate=empty,results=[]}).

View File

@ -1,9 +0,0 @@
include ../support/include.mk
all: $(EBIN_FILES)
debug:
$(MAKE) DEBUG=-DDEBUG
clean:
rm -rf $(EBIN_FILES) erl_crash.dump

View File

@ -1,250 +0,0 @@
-module(client).
-behavior(gen_server).
-export([start/1, start/2, connect/1, connect/2, asend/2, send/3, send/2,
disconnect/1, ssend/3, str/1, format/1, sformat/1, ssend/2,
get_all_results/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-include("erldis.hrl").
-define(EOL, "\r\n").
%% Helpers
str(X) when is_list(X) ->
X;
str(X) when is_atom(X) ->
atom_to_list(X);
str(X) when is_binary(X) ->
binary_to_list(X);
str(X) when is_integer(X) ->
integer_to_list(X);
str(X) when is_float(X) ->
float_to_list(X).
format([], Result) ->
string:join(lists:reverse(Result), ?EOL);
format([Line|Rest], Result) ->
JoinedLine = string:join([str(X) || X <- Line], " "),
format(Rest, [JoinedLine|Result]).
format(Lines) ->
format(Lines, []).
sformat(Line) ->
format([Line], []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Exported API
start(Host) ->
connect(Host).
start(Host, Port) ->
connect(Host, Port).
connect(Host) ->
connect(Host, 6379).
connect(Host, Port) ->
gen_server:start_link(?MODULE, [Host, Port], []).
% This is the simple send with a single row of commands
ssend(Client, Cmd) -> ssend(Client, Cmd, []).
ssend(Client, Cmd, Args) ->
gen_server:cast(Client, {send, sformat([Cmd|Args])}).
% This is the complete send with multiple rows
send(Client, Cmd) -> send(Client, Cmd, []).
send(Client, Cmd, Args) ->
gen_server:cast(Client, {send,
string:join([str(Cmd), format(Args)], " ")}).
% asynchronous send, we don't care about the result.
asend(Client, Cmd) ->
gen_server:cast(Client, {asend, Cmd}).
disconnect(Client) ->
gen_server:call(Client, disconnect).
get_all_results(Client) ->
gen_server:call(Client, get_all_results).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% gen_server callbacks
init([Host, Port]) ->
process_flag(trap_exit, true),
ConnectOptions = [list, {active, once}, {packet, line}, {nodelay, true}],
case gen_tcp:connect(Host, Port, ConnectOptions) of
{error, Why} ->
{error, {socket_error, Why}};
{ok, Socket} ->
{ok, #redis{socket=Socket, calls=0}}
end.
handle_call({send, Cmd}, From, State) ->
gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
{noreply, State#redis{reply_caller=fun(V) -> gen_server:reply(From, lists:nth(1, V)) end,
remaining=1}};
handle_call(disconnect, _From, State) ->
{stop, normal, ok, State};
handle_call(get_all_results, From, State) ->
case State#redis.calls of
0 ->
% answers came earlier than we could start listening...
% Very unlikely but totally possible.
{reply, lists:reverse(State#redis.results), State#redis{results=[], calls=0}};
_ ->
% We are here earlier than results came, so just make
% ourselves wait until stuff is ready.
{noreply, State#redis{reply_caller=fun(V) -> gen_server:reply(From, V) end}}
end;
handle_call(_, _From, State) -> {noreply, State}.
handle_cast({asend, Cmd}, State) ->
gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
{noreply, State};
handle_cast({send, Cmd}, State=#redis{remaining=Remaining, calls=Calls}) ->
% how we should do here: if remaining is already != 0 then we'll
% let handle_info take care of keeping track how many remaining things
% there are. If instead it's 0 we are the first call so let's just
% do it.
gen_tcp:send(State#redis.socket, [Cmd|?EOL]),
case Remaining of
0 ->
{noreply, State#redis{remaining=1, calls=1}};
_ ->
{noreply, State#redis{calls=Calls+1}}
end;
handle_cast(_Msg, State) -> {noreply, State}.
trim2({ok, S}) ->
string:substr(S, 1, length(S)-2);
trim2(S) ->
trim2({ok, S}).
% This function helps with pipelining by creating a pubsub system with
% the caller. The caller could submit multiple requests and not listen
% until later when all or some of them have been answered, at that
% point 2 conditions can be true:
% 1) We still need to process more things in this response chain
% 2) We are finished.
%
% And these 2 are together with the following 2:
% 1) We called get_all_results before the end of the responses.
% 2) We called get_all_results after the end of the responses.
%
% If there's stuff missing in the chain we just push results, this also
% happens when there's nothing more to process BUT we haven't requested
% results yet.
% In case we have requested results: if requests are not yet ready we
% just push them, otherwise we finally answer all of them.
save_or_reply(Result, State=#redis{calls=Calls, results=Results, reply_caller=ReplyCaller}) ->
case Calls of
0 ->
% We don't reverse results here because if all the requests
% come in and then we submit another one, if we reverse
% they will be scrambled in the results field of the record.
% instead if we wait just before we reply they will be
% in the right order.
FullResults = [Result|Results],
NewState = case ReplyCaller of
undefined ->
State#redis{results=FullResults};
_ ->
ReplyCaller(lists:reverse(FullResults)),
State#redis{results=[]}
end,
NewState#redis{remaining=0, pstate=empty,
reply_caller=undefined, buffer=[],
calls=0};
_ ->
State#redis{results=[Result|Results], remaining=1, pstate=empty, buffer=[], calls=Calls}
end.
handle_info({tcp, Socket, Data}, State=#redis{calls=Calls}) ->
Trimmed = trim2(Data),
NewState = case {State#redis.remaining-1, proto:parse(State#redis.pstate, Trimmed)} of
% This line contained an error code. Next line will hold
% The error message that we will parse.
{0, error} ->
State#redis{remaining=1, pstate=error};
% The stateful parser just started and tells us the number
% of results that we will have to parse for those calls
% where more than one result is expected. The next
% line will start with the first item to read.
{0, {hold, Remaining}} ->
case Remaining of
nil ->
save_or_reply(nil, State#redis{calls=Calls-1});
_ ->
% Reset the remaining value to the number of results that we need to parse.
State#redis{remaining=Remaining, pstate=read}
end;
% We either had only one thing to read or we are at the
% end of the stuff that we need to read. either way
% just pack up the buffer and send.
{0, {read, NBytes}} ->
CurrentValue = case NBytes of
nil ->
nil;
_ ->
inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes
CV = trim2(gen_tcp:recv(Socket, NBytes+2)), % also consume the \r\n
inet:setopts(Socket, [{packet, line}]), % go back to line mode
CV
end,
OldBuffer = State#redis.buffer,
case OldBuffer of
[] ->
save_or_reply(CurrentValue, State#redis{calls=Calls-1});
_ ->
save_or_reply(lists:reverse([CurrentValue|OldBuffer]), State#redis{calls=Calls-1})
end;
% The stateful parser tells us to read some bytes
{N, {read, NBytes}} ->
% annoying repetition... I should reuse this code.
CurrentValue = case NBytes of
nil ->
nil;
_ ->
inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes
CV = trim2(gen_tcp:recv(Socket, NBytes+2)), % also consume the \r\n
inet:setopts(Socket, [{packet, line}]), % go back to line mode
CV
end,
OldBuffer = State#redis.buffer,
State#redis{remaining=N, buffer=[CurrentValue|OldBuffer], pstate=read};
% Simple return values contained in a single line
{0, Value} ->
save_or_reply(Value, State#redis{calls=Calls-1})
end,
inet:setopts(Socket, [{active, once}]),
{noreply, NewState};
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, State) ->
case State#redis.socket of
undefined ->
pass;
Socket ->
gen_tcp:close(Socket)
end,
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View File

@ -1,112 +0,0 @@
-module(erldis).
-compile(export_all).
-define(EOL, "\r\n").
%% helpers
flatten({error, Message}) ->
{error, Message};
flatten(List) when is_list(List)->
lists:flatten(List).
%% exposed API
connect(Host) ->
client:connect(Host).
quit(Client) ->
client:asend(Client, "QUIT"),
client:disconnect(Client).
%% Commands operating on string values
internal_set_like(Client, Command, Key, Value) ->
client:send(Client, Command, [[Key, length(Value)],
[Value]]).
get_all_results(Client) -> client:get_all_results(Client).
auth(Client, Password) -> client:ssend(Client, auth, [Password]).
set(Client, Key, Value) -> internal_set_like(Client, set, Key, Value).
get(Client, Key) -> client:ssend(Client, get, [Key]).
getset(Client, Key, Value) -> internal_set_like(Client, getset, Key, Value).
mget(Client, Keys) -> client:ssend(Client, mget, Keys).
setnx(Client, Key, Value) -> internal_set_like(Client, setnx, Key, Value).
incr(Client, Key) -> client:ssend(Client, incr, [Key]).
incrby(Client, Key, By) -> client:ssend(Client, incrby, [Key, By]).
decr(Client, Key) -> client:ssend(Client, decr, [Key]).
decrby(Client, Key, By) -> client:ssend(Client, decrby, [Key, By]).
%% Commands operating on every value
exists(Client, Key) -> client:ssend(Client, exists, [Key]).
del(Client, Key) -> client:ssend(Client, del, [Key]).
type(Client, Key) -> client:ssend(Client, type, [Key]).
keys(Client, Pattern) -> client:ssend(Client, keys, [Pattern]).
randomkey(Client, Key) -> client:ssend(Client, randomkey, [Key]).
rename(Client, OldKey, NewKey) -> client:ssend(Client, rename, [OldKey, NewKey]).
renamenx(Client, OldKey, NewKey) -> client:ssend(Client, renamenx, [OldKey, NewKey]).
dbsize(Client) -> client:ssend(Client, dbsize).
expire(Client, Key, Seconds) -> client:ssend(Client, expire, [Key, Seconds]).
ttl(Client, Key) -> client:ssend(Client, ttl, [Key]).
%% Commands operating on lists
rpush(Client, Key, Value) -> internal_set_like(Client, rpush, Key, Value).
lpush(Client, Key, Value) -> internal_set_like(Client, lpush, Key, Value).
llen(Client, Key) -> client:ssend(Client, llen, [Key]).
lrange(Client, Key, Start, End) -> client:ssend(Client, lrange, [Key, Start, End]).
ltrim(Client, Key, Start, End) -> client:ssend(Client, ltrim, [Key, Start, End]).
lindex(Client, Key, Index) -> client:ssend(Client, lindex, [Key, Index]).
lset(Client, Key, Index, Value) ->
client:send(Client, lset, [[Key, Index, length(Value)],
[Value]]).
lrem(Client, Key, Number, Value) ->
client:send(Client, lrem, [[Key, Number, length(Value)],
[Value]]).
lpop(Client, Key) -> client:ssend(Client, lpop, [Key]).
rpop(Client, Key) -> client:ssend(Client, rpop, [Key]).
%% Commands operating on sets
sadd(Client, Key, Value) -> internal_set_like(Client, sadd, Key, Value).
srem(Client, Key, Value) -> internal_set_like(Client, srem, Key, Value).
smove(Client, SrcKey, DstKey, Member) -> client:send(Client, smove, [[SrcKey, DstKey, length(Member)],
[Member]]).
scard(Client, Key) -> client:ssend(Client, scard, [Key]).
sismember(Client, Key, Value) -> internal_set_like(Client, sismember, Key, Value).
sintersect(Client, Keys) -> client:ssend(Client, sinter, Keys).
sinter(Client, Keys) -> sintersect(Client, Keys).
sinterstore(Client, DstKey, Keys) -> client:ssend(Client, sinterstore, [DstKey|Keys]).
sunion(Client, Keys) -> client:ssend(Client, sunion, Keys).
sunionstore(Client, DstKey, Keys) -> client:ssend(Client, sunionstore, [DstKey|Keys]).
sdiff(Client, Keys) -> client:ssend(Client, sdiff, Keys).
sdiffstore(Client, DstKey, Keys) -> client:ssend(Client, sdiffstore, [DstKey|Keys]).
smembers(Client, Key) -> client:ssend(Client, smembers, [Key]).
%% Multiple DB commands
select(Client, Index) -> client:ssend(Client, select, [Index]).
move(Client, Key, DBIndex) -> client:ssend(Client, move, [Key, DBIndex]).
flushdb(Client) -> client:ssend(Client, flushdb).
flushall(Client) -> client:ssend(Client, flushall).
%% Commands operating on both lists and sets
sort(Client, Key) -> client:ssend(Client, sort, [Key]).
sort(Client, Key, Extra) -> client:ssend(Client, sort, [Key, Extra]).
%% Persistence control commands
save(Client) -> client:ssend(Client, save).
bgsave(Client) -> client:ssend(Client, bgsave).
lastsave(Client) -> client:ssend(Client, lastsave).
shutdown(Client) -> client:asend(Client, shutdown).
%% Remote server control commands
info(Client) -> client:ssend(Client, info).
slaveof(Client, Host, Port) -> client:ssend(Client, slaveof, [Host, Port]).
slaveof(Client) -> client:ssend(Client, slaveof, ["no one"]).

View File

@ -1,38 +0,0 @@
-module(proto).
-export([parse/2]).
parse(empty, "+OK") ->
ok;
parse(empty, "+PONG") ->
pong;
parse(empty, ":0") ->
false;
parse(empty, ":1") ->
true;
parse(empty, "-" ++ Message) ->
{error, Message};
parse(empty, "$-1") ->
{read, nil};
parse(empty, "*-1") ->
{hold, nil};
parse(empty, "$" ++ BulkSize) ->
{read, list_to_integer(BulkSize)};
parse(read, "$" ++ BulkSize) ->
{read, list_to_integer(BulkSize)};
parse(empty, "*" ++ MultiBulkSize) ->
{hold, list_to_integer(MultiBulkSize)};
parse(empty, Message) ->
convert(Message).
convert(":" ++ Message) ->
list_to_integer(Message);
% in case the message is not OK or PONG it's a
% real value that we don't know how to convert
% to an atom, so just pass it as is and remove
% the +
convert("+" ++ Message) ->
Message;
convert(Message) ->
Message.

View File

@ -1,51 +0,0 @@
## -*- makefile -*-
ERL := erl
ERLC := $(ERL)c
INCLUDE_DIRS := ../include $(wildcard ../deps/*/include)
EBIN_DIRS := $(wildcard ../deps/*/ebin)
ERLC_FLAGS := -W $(INCLUDE_DIRS:../%=-I ../%) $(EBIN_DIRS:%=-pa %)
ifndef no_debug_info
ERLC_FLAGS += +debug_info
endif
ifdef debug
ERLC_FLAGS += -Ddebug
endif
ifdef test
ERLC_FLAGS += -DTEST
endif
EBIN_DIR := ../ebin
DOC_DIR := ../doc
EMULATOR := beam
ERL_TEMPLATE := $(wildcard *.et)
ERL_SOURCES := $(wildcard *.erl)
ERL_HEADERS := $(wildcard *.hrl) $(wildcard ../include/*.hrl)
ERL_OBJECTS := $(ERL_SOURCES:%.erl=$(EBIN_DIR)/%.beam)
ERL_TEMPLATES := $(ERL_TEMPLATE:%.et=$(EBIN_DIR)/%.beam)
ERL_OBJECTS_LOCAL := $(ERL_SOURCES:%.erl=./%.$(EMULATOR))
APP_FILES := $(wildcard *.app)
EBIN_FILES = $(ERL_OBJECTS) $(APP_FILES:%.app=../ebin/%.app) $(ERL_TEMPLATES)
MODULES = $(ERL_SOURCES:%.erl=%)
../ebin/%.app: %.app
cp $< $@
$(EBIN_DIR)/%.$(EMULATOR): %.erl
$(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $<
$(EBIN_DIR)/%.$(EMULATOR): %.et
$(ERL) -noshell -pa ../../elib/erltl/ebin/ -eval "erltl:compile(atom_to_list('$<'), [{outdir, \"../ebin\"}, report_errors, report_warnings, nowarn_unused_vars])." -s init stop
./%.$(EMULATOR): %.erl
$(ERLC) $(ERLC_FLAGS) -o . $<
$(DOC_DIR)/%.html: %.erl
$(ERL) -noshell -run edoc file $< -run init stop
mv *.html $(DOC_DIR)

View File

@ -1,12 +0,0 @@
include ../support/include.mk
all: $(EBIN_FILES)
clean:
rm -rf $(EBIN_FILES) erl_crash.dump
test: $(MODULES)
./$(MODULES):
@echo "Running tests for $@"
erl -pa ../ebin -run $@ test -run init stop -noshell

View File

@ -1,91 +0,0 @@
-module(erldis_tests).
-include_lib("eunit/include/eunit.hrl").
-include("erldis.hrl").
quit_test() ->
{ok, Client} = erldis:connect("localhost"),
ok = erldis:quit(Client),
false = is_process_alive(Client).
utils_test() ->
?assertEqual(client:str(1), "1"),
?assertEqual(client:str(atom), "atom"),
?assertEqual(client:format([[1, 2, 3]]), "1 2 3"),
?assertEqual(client:format([[1,2,3], [4,5,6]]), "1 2 3\r\n4 5 6").
basic_test() ->
{ok, Client} = erldis:connect("localhost"),
erldis:flushall(Client),
erldis:get(Client, "pippo"),
erldis:set(Client, "hello", "kitty!"),
erldis:setnx(Client, "foo", "bar"),
erldis:setnx(Client, "foo", "bar"),
[ok, nil, ok, true, false] = erldis:get_all_results(Client),
erldis:exists(Client, "hello"),
erldis:exists(Client, "foo"),
erldis:get(Client, "foo"),
erldis:mget(Client, ["hello", "foo"]),
erldis:del(Client, "hello"),
erldis:del(Client, "foo"),
erldis:exists(Client, "hello"),
erldis:exists(Client, "foo"),
[true, true, "bar", ["kitty!", "bar"], true, true, false, false] = erldis:get_all_results(Client),
erldis:set(Client, "pippo", "pluto"),
erldis:sadd(Client, "pippo", "paperino"),
% foo doesn't exist, the result will be nil
erldis:lrange(Client, "foo", 1, 2),
erldis:lrange(Client, "pippo", 1, 2),
[ok,
{error, "ERR Operation against a key holding the wrong kind of value"},
nil,
{error, "ERR Operation against a key holding the wrong kind of value"}
] = erldis:get_all_results(Client),
erldis:del(Client, "pippo"),
[true] = erldis:get_all_results(Client),
erldis:rpush(Client, "a_list", "1"),
erldis:rpush(Client, "a_list", "2"),
erldis:rpush(Client, "a_list", "3"),
erldis:rpush(Client, "a_list", "1"),
erldis:lrem(Client, "a_list", 1, "1"),
erldis:lrange(Client, "a_list", 0, 2),
[ok, ok, ok, ok, true, ["2", "3", "1"]] = erldis:get_all_results(Client),
erldis:sort(Client, "a_list"),
erldis:sort(Client, "a_list", "DESC"),
erldis:lrange(Client, "a_list", 0, 2),
erldis:sort(Client, "a_list", "LIMIT 0 2 ASC"),
[["1", "2", "3"], ["3", "2", "1"], ["2", "3", "1"],
["1", "2"]] = erldis:get_all_results(Client),
ok = erldis:quit(Client).
% inline_tests(Client) ->
% [?_assertMatch(ok, erldis:set(Client, "hello", "kitty!")),
% ?_assertMatch(false, erldis:setnx(Client, "hello", "kitty!")),
% ?_assertMatch(true, erldis:exists(Client, "hello")),
% ?_assertMatch(true, erldis:del(Client, "hello")),
% ?_assertMatch(false, erldis:exists(Client, "hello")),
%
% ?_assertMatch(true, erldis:setnx(Client, "hello", "kitty!")),
% ?_assertMatch(true, erldis:exists(Client, "hello")),
% ?_assertMatch("kitty!", erldis:get(Client, "hello")),
% ?_assertMatch(true, erldis:del(Client, "hello")),
%
%
% ?_assertMatch(1, erldis:incr(Client, "pippo"))
% ,?_assertMatch(2, erldis:incr(Client, "pippo"))
% ,?_assertMatch(1, erldis:decr(Client, "pippo"))
% ,?_assertMatch(0, erldis:decr(Client, "pippo"))
% ,?_assertMatch(-1, erldis:decr(Client, "pippo"))
%
% ,?_assertMatch(6, erldis:incrby(Client, "pippo", 7))
% ,?_assertMatch(2, erldis:decrby(Client, "pippo", 4))
% ,?_assertMatch(-2, erldis:decrby(Client, "pippo", 4))
% ,?_assertMatch(true, erldis:del(Client, "pippo"))
% ].

View File

@ -1,10 +0,0 @@
-module(proto_tests).
-include_lib("eunit/include/eunit.hrl").
parse_test() ->
ok = proto:parse(empty, "+OK"),
pong = proto:parse(empty, "+PONG"),
false = proto:parse(empty, ":0"),
true = proto:parse(empty, ":1"),
{error, "1"} = proto:parse(empty, "-1").

View File

@ -1,22 +0,0 @@
Copyright (c) 2009
Daniele Alessandri
http://www.clorophilla.net/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,4 +0,0 @@
redis-lua
-------------------------------------------------------------------------------
A Lua client library for the redis key value storage system.

View File

@ -1,347 +0,0 @@
local _G = _G
local require, error, type, print = require, error, type, print
local table, pairs, tostring, tonumber = table, pairs, tostring, tonumber
module('Redis')
local socket = require('socket') -- requires LuaSocket as a dependency
local redis_commands = {}
local network, request, response, utils = {}, {}, {}, {}, {}
local protocol = { newline = '\r\n', ok = 'OK', err = 'ERR', null = 'nil' }
local function toboolean(value) return value == 1 end
local function load_methods(proto, methods)
local redis = _G.setmetatable ({}, _G.getmetatable(proto))
for i, v in pairs(proto) do redis[i] = v end
for i, v in pairs(methods) do redis[i] = v end
return redis
end
-- ############################################################################
function network.write(client, buffer)
local _, err = client.socket:send(buffer)
if err then error(err) end
end
function network.read(client, len)
if len == nil then len = '*l' end
local line, err = client.socket:receive(len)
if not err then return line else error('Connection error: ' .. err) end
end
-- ############################################################################
function response.read(client)
local res = network.read(client)
local prefix = res:sub(1, -#res)
local response_handler = protocol.prefixes[prefix]
if not response_handler then
error("Unknown response prefix: " .. prefix)
else
return response_handler(client, res)
end
end
function response.status(client, data)
local sub = data:sub(2)
if sub == protocol.ok then return true else return sub end
end
function response.error(client, data)
local err_line = data:sub(2)
if err_line:sub(1, 3) == protocol.err then
error("Redis error: " .. err_line:sub(5))
else
error("Redis error: " .. err_line)
end
end
function response.bulk(client, data)
local str = data:sub(2)
local len = tonumber(str)
if not len then
error('Cannot parse ' .. str .. ' as data length.')
else
if len == -1 then return nil end
local next_chunk = network.read(client, len + 2)
return next_chunk:sub(1, -3);
end
end
function response.multibulk(client, data)
local str = data:sub(2)
-- TODO: add a check if the returned value is indeed a number
local list_count = tonumber(str)
if list_count == -1 then
return nil
else
local list = {}
if list_count > 0 then
for i = 1, list_count do
table.insert(list, i, response.bulk(client, network.read(client)))
end
end
return list
end
end
function response.integer(client, data)
local res = data:sub(2)
local number = tonumber(res)
if not number then
if res == protocol.null then
return nil
else
error('Cannot parse ' .. res .. ' as numeric response.')
end
end
return number
end
protocol.prefixes = {
['+'] = response.status,
['-'] = response.error,
['$'] = response.bulk,
['*'] = response.multibulk,
[':'] = response.integer,
}
-- ############################################################################
function request.raw(client, buffer)
-- TODO: optimize
local bufferType = type(buffer)
if bufferType == 'string' then
network.write(client, buffer)
elseif bufferType == 'table' then
network.write(client, table.concat(buffer))
else
error('Argument error: ' .. bufferType)
end
return response.read(client)
end
function request.inline(client, command, ...)
if arg.n == 0 then
network.write(client, command .. protocol.newline)
else
local arguments = arg
arguments.n = nil
if #arguments > 0 then
arguments = table.concat(arguments, ' ')
else
arguments = ''
end
network.write(client, command .. ' ' .. arguments .. protocol.newline)
end
return response.read(client)
end
function request.bulk(client, command, ...)
local arguments = arg
local data = tostring(table.remove(arguments))
arguments.n = nil
-- TODO: optimize
if #arguments > 0 then
arguments = table.concat(arguments, ' ')
else
arguments = ''
end
return request.raw(client, {
command, ' ', arguments, ' ', #data, protocol.newline, data, protocol.newline
})
end
-- ############################################################################
local function custom(command, send, parse)
return function(self, ...)
local reply = send(self, command, ...)
if parse then
return parse(reply, command, ...)
else
return reply
end
end
end
local function bulk(command, reader)
return custom(command, request.bulk, reader)
end
local function inline(command, reader)
return custom(command, request.inline, reader)
end
-- ############################################################################
function connect(host, port)
local client_socket = socket.connect(host, port)
if not client_socket then
error('Could not connect to ' .. host .. ':' .. port)
end
local redis_client = {
socket = client_socket,
raw_cmd = function(self, buffer)
return request.raw(self, buffer .. protocol.newline)
end,
}
return load_methods(redis_client, redis_commands)
end
-- ############################################################################
redis_commands = {
-- miscellaneous commands
ping = inline('PING',
function(response)
if response == 'PONG' then return true else return false end
end
),
echo = bulk('ECHO'),
-- TODO: the server returns an empty -ERR on authentication failure
auth = inline('AUTH'),
-- connection handling
quit = custom('QUIT',
function(client, command)
-- let's fire and forget! the connection is closed as soon
-- as the QUIT command is received by the server.
network.write(client, command .. protocol.newline)
end
),
-- commands operating on string values
set = bulk('SET'),
set_preserve = bulk('SETNX', toboolean),
get = inline('GET'),
get_multiple = inline('MGET'),
get_set = bulk('GETSET'),
increment = inline('INCR'),
increment_by = inline('INCRBY'),
decrement = inline('DECR'),
decrement_by = inline('DECRBY'),
exists = inline('EXISTS', toboolean),
delete = inline('DEL', toboolean),
type = inline('TYPE'),
-- commands operating on the key space
keys = inline('KEYS',
function(response)
local keys = {}
response:gsub('%w+', function(key)
table.insert(keys, key)
end)
return keys
end
),
random_key = inline('RANDOMKEY'),
rename = inline('RENAME'),
rename_preserve = inline('RENAMENX'),
expire = inline('EXPIRE', toboolean),
database_size = inline('DBSIZE'),
time_to_live = inline('TTL'),
-- commands operating on lists
push_tail = bulk('RPUSH'),
push_head = bulk('LPUSH'),
list_length = inline('LLEN'),
list_range = inline('LRANGE'),
list_trim = inline('LTRIM'),
list_index = inline('LINDEX'),
list_set = bulk('LSET'),
list_remove = bulk('LREM'),
pop_first = inline('LPOP'),
pop_last = inline('RPOP'),
-- commands operating on sets
set_add = bulk('SADD'),
set_remove = bulk('SREM'),
set_move = bulk('SMOVE'),
set_cardinality = inline('SCARD'),
set_is_member = inline('SISMEMBER'),
set_intersection = inline('SINTER'),
set_intersection_store = inline('SINTERSTORE'),
set_union = inline('SUNION'),
set_union_store = inline('SUNIONSTORE'),
set_diff = inline('SDIFF'),
set_diff_store = inline('SDIFFSTORE'),
set_members = inline('SMEMBERS'),
-- multiple databases handling commands
select_database = inline('SELECT'),
move_key = inline('MOVE'),
flush_database = inline('FLUSHDB'),
flush_databases = inline('FLUSHALL'),
-- sorting
--[[
TODO: should we pass sort parameters as a table? e.g:
params = {
by = 'weight_*',
get = 'object_*',
limit = { 0, 10 },
sort = { 'desc', 'alpha' }
}
--]]
sort = custom('SORT',
function(client, command, params)
-- TODO: here we will put the logic needed to serialize the params
-- table to be sent as the argument of the SORT command.
return request.inline(client, command, params)
end
),
-- persistence control commands
save = inline('SAVE'),
background_save = inline('BGSAVE'),
last_save = inline('LASTSAVE'),
shutdown = custom('SHUTDOWN',
function(client, command)
-- let's fire and forget! the connection is closed as soon
-- as the SHUTDOWN command is received by the server.
network.write(client, command .. protocol.newline)
end
),
-- remote server control commands
info = inline('INFO',
function(response)
local info = {}
response:gsub('([^\r\n]*)\r\n', function(kv)
local k,v = kv:match(('([^:]*):([^:]*)'):rep(1))
info[k] = v
end)
return info
end
),
slave_of = inline('SLAVEOF'),
slave_of_no_one = custom('SLAVEOF',
function(client, command)
return request.inline(client, command, 'NO ONE')
end
),
}

View File

@ -1,8 +0,0 @@
Revision history for Redis
0.01 Sun Mar 22 19:02:17 CET 2009
First version, tracking git://github.com/antirez/redis
0.08 Tue Mar 24 22:38:59 CET 2009
This version supports new protocol introduced in beta 8
Version bump to be in-sync with Redis version

View File

@ -1,8 +0,0 @@
Changes
MANIFEST
Makefile.PL
README
lib/Redis.pm
t/00-load.t
t/pod-coverage.t
t/pod.t

View File

@ -1,19 +0,0 @@
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'Redis',
AUTHOR => 'Dobrica Pavlinusic <dpavlin@rot13.org>',
VERSION_FROM => 'lib/Redis.pm',
ABSTRACT_FROM => 'lib/Redis.pm',
PL_FILES => {},
PREREQ_PM => {
'Test::More' => 0,
'IO::Socket::INET' => 0,
'Data::Dump' => 0,
'Carp' => 0,
},
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'Redis-*' },
);

View File

@ -1,43 +0,0 @@
Redis
Perl binding for Redis database which is in-memory hash store with
support for scalars, arrays and sets and disk persistence.
INSTALLATION
To install this module, run the following commands:
perl Makefile.PL
make
make test
make install
SUPPORT AND DOCUMENTATION
After installing, you can find documentation for this module with the
perldoc command.
perldoc Redis
You can also look for information at:
RT, CPAN's request tracker
http://rt.cpan.org/NoAuth/Bugs.html?Dist=Redis
AnnoCPAN, Annotated CPAN documentation
http://annocpan.org/dist/Redis
CPAN Ratings
http://cpanratings.perl.org/d/Redis
Search CPAN
http://search.cpan.org/dist/Redis
COPYRIGHT AND LICENCE
Copyright (C) 2009 Dobrica Pavlinusic
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

View File

@ -1,422 +0,0 @@
package Redis;
use warnings;
use strict;
use IO::Socket::INET;
use Data::Dump qw/dump/;
use Carp qw/confess/;
=head1 NAME
Redis - perl binding for Redis database
=cut
our $VERSION = '0.08';
=head1 DESCRIPTION
Pure perl bindings for L<http://code.google.com/p/redis/>
This version support git version 0.08 of Redis available at
L<git://github.com/antirez/redis>
This documentation
lists commands which are exercised in test suite, but
additinal commands will work correctly since protocol
specifies enough information to support almost all commands
with same peace of code with a little help of C<AUTOLOAD>.
=head1 FUNCTIONS
=head2 new
my $r = Redis->new;
=cut
our $debug = $ENV{REDIS} || 0;
our $sock;
my $server = '127.0.0.1:6379';
sub new {
my $class = shift;
my $self = {};
bless($self, $class);
warn "# opening socket to $server";
$sock ||= IO::Socket::INET->new(
PeerAddr => $server,
Proto => 'tcp',
) || die $!;
$self;
}
my $bulk_command = {
set => 1, setnx => 1,
rpush => 1, lpush => 1,
lset => 1, lrem => 1,
sadd => 1, srem => 1,
sismember => 1,
echo => 1,
};
# we don't want DESTROY to fallback into AUTOLOAD
sub DESTROY {}
our $AUTOLOAD;
sub AUTOLOAD {
my $self = shift;
my $command = $AUTOLOAD;
$command =~ s/.*://;
warn "## $command ",dump(@_) if $debug;
my $send;
if ( defined $bulk_command->{$command} ) {
my $value = pop;
$value = '' if ! defined $value;
$send
= uc($command)
. ' '
. join(' ', @_)
. ' '
. length( $value )
. "\r\n$value\r\n"
;
} else {
$send
= uc($command)
. ' '
. join(' ', @_)
. "\r\n"
;
}
warn ">> $send" if $debug;
print $sock $send;
if ( $command eq 'quit' ) {
close( $sock ) || die "can't close socket: $!";
return 1;
}
my $result = <$sock> || die "can't read socket: $!";
warn "<< $result" if $debug;
my $type = substr($result,0,1);
$result = substr($result,1,-2);
if ( $command eq 'info' ) {
my $hash;
foreach my $l ( split(/\r\n/, __sock_read_bulk($result) ) ) {
my ($n,$v) = split(/:/, $l, 2);
$hash->{$n} = $v;
}
return $hash;
} elsif ( $command eq 'keys' ) {
my $keys = __sock_read_bulk($result);
return split(/\s/, $keys) if $keys;
return;
}
if ( $type eq '-' ) {
confess $result;
} elsif ( $type eq '+' ) {
return $result;
} elsif ( $type eq '$' ) {
return __sock_read_bulk($result);
} elsif ( $type eq '*' ) {
return __sock_read_multi_bulk($result);
} elsif ( $type eq ':' ) {
return $result; # FIXME check if int?
} else {
confess "unknown type: $type", __sock_read_line();
}
}
sub __sock_read_bulk {
my $len = shift;
return undef if $len < 0;
my $v;
if ( $len > 0 ) {
read($sock, $v, $len) || die $!;
warn "<< ",dump($v),$/ if $debug;
}
my $crlf;
read($sock, $crlf, 2); # skip cr/lf
return $v;
}
sub __sock_read_multi_bulk {
my $size = shift;
return undef if $size < 0;
$size--;
my @list = ( 0 .. $size );
foreach ( 0 .. $size ) {
$list[ $_ ] = __sock_read_bulk( substr(<$sock>,1,-2) );
}
warn "## list = ", dump( @list ) if $debug;
return @list;
}
1;
__END__
=head1 Connection Handling
=head2 quit
$r->quit;
=head2 ping
$r->ping || die "no server?";
=head1 Commands operating on string values
=head2 set
$r->set( foo => 'bar' );
$r->setnx( foo => 42 );
=head2 get
my $value = $r->get( 'foo' );
=head2 mget
my @values = $r->mget( 'foo', 'bar', 'baz' );
=head2 incr
$r->incr('counter');
$r->incrby('tripplets', 3);
=head2 decr
$r->decr('counter');
$r->decrby('tripplets', 3);
=head2 exists
$r->exists( 'key' ) && print "got key!";
=head2 del
$r->del( 'key' ) || warn "key doesn't exist";
=head2 type
$r->type( 'key' ); # = string
=head1 Commands operating on the key space
=head2 keys
my @keys = $r->keys( '*glob_pattern*' );
=head2 randomkey
my $key = $r->randomkey;
=head2 rename
my $ok = $r->rename( 'old-key', 'new-key', $new );
=head2 dbsize
my $nr_keys = $r->dbsize;
=head1 Commands operating on lists
See also L<Redis::List> for tie interface.
=head2 rpush
$r->rpush( $key, $value );
=head2 lpush
$r->lpush( $key, $value );
=head2 llen
$r->llen( $key );
=head2 lrange
my @list = $r->lrange( $key, $start, $end );
=head2 ltrim
my $ok = $r->ltrim( $key, $start, $end );
=head2 lindex
$r->lindex( $key, $index );
=head2 lset
$r->lset( $key, $index, $value );
=head2 lrem
my $modified_count = $r->lrem( $key, $count, $value );
=head2 lpop
my $value = $r->lpop( $key );
=head2 rpop
my $value = $r->rpop( $key );
=head1 Commands operating on sets
=head2 sadd
$r->sadd( $key, $member );
=head2 srem
$r->srem( $key, $member );
=head2 scard
my $elements = $r->scard( $key );
=head2 sismember
$r->sismember( $key, $member );
=head2 sinter
$r->sinter( $key1, $key2, ... );
=head2 sinterstore
my $ok = $r->sinterstore( $dstkey, $key1, $key2, ... );
=head1 Multiple databases handling commands
=head2 select
$r->select( $dbindex ); # 0 for new clients
=head2 move
$r->move( $key, $dbindex );
=head2 flushdb
$r->flushdb;
=head2 flushall
$r->flushall;
=head1 Sorting
=head2 sort
$r->sort("key BY pattern LIMIT start end GET pattern ASC|DESC ALPHA');
=head1 Persistence control commands
=head2 save
$r->save;
=head2 bgsave
$r->bgsave;
=head2 lastsave
$r->lastsave;
=head2 shutdown
$r->shutdown;
=head1 Remote server control commands
=head2 info
my $info_hash = $r->info;
=head1 AUTHOR
Dobrica Pavlinusic, C<< <dpavlin at rot13.org> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-redis at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Redis>. I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Redis
perldoc Redis::List
perldoc Redis::Hash
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Redis>
=item * AnnoCPAN: Annotated CPAN documentation
L<http://annocpan.org/dist/Redis>
=item * CPAN Ratings
L<http://cpanratings.perl.org/d/Redis>
=item * Search CPAN
L<http://search.cpan.org/dist/Redis>
=back
=head1 ACKNOWLEDGEMENTS
=head1 COPYRIGHT & LICENSE
Copyright 2009 Dobrica Pavlinusic, all rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
1; # End of Redis

View File

@ -1,70 +0,0 @@
package Redis::Hash;
use strict;
use warnings;
use Tie::Hash;
use base qw/Redis Tie::StdHash/;
use Data::Dump qw/dump/;
=head1 NAME
Redis::Hash - tie perl hashes into Redis
=head1 SYNOPSYS
tie %name, 'Redis::Hash', 'prefix';
=cut
# mandatory methods
sub TIEHASH {
my ($class,$name) = @_;
my $self = Redis->new;
$name .= ':' if $name;
$self->{name} = $name || '';
bless $self => $class;
}
sub STORE {
my ($self,$key,$value) = @_;
$self->set( $self->{name} . $key, $value );
}
sub FETCH {
my ($self,$key) = @_;
$self->get( $self->{name} . $key );
}
sub FIRSTKEY {
my $self = shift;
$self->{keys} = [ $self->keys( $self->{name} . '*' ) ];
$self->NEXTKEY;
}
sub NEXTKEY {
my $self = shift;
my $key = shift @{ $self->{keys} } || return;
my $name = $self->{name};
$key =~ s{^$name}{} || warn "can't strip $name from $key";
return $key;
}
sub EXISTS {
my ($self,$key) = @_;
$self->exists( $self->{name} . $key );
}
sub DELETE {
my ($self,$key) = @_;
$self->del( $self->{name} . $key );
}
sub CLEAR {
my ($self) = @_;
$self->del( $_ ) foreach ( $self->keys( $self->{name} . '*' ) );
$self->{keys} = [];
}
1;

View File

@ -1,85 +0,0 @@
package Redis::List;
use strict;
use warnings;
use base qw/Redis Tie::Array/;
=head1 NAME
Redis::List - tie perl arrays into Redis lists
=head1 SYNOPSYS
tie @a, 'Redis::List', 'name';
=cut
# mandatory methods
sub TIEARRAY {
my ($class,$name) = @_;
my $self = $class->new;
$self->{name} = $name;
bless $self => $class;
}
sub FETCH {
my ($self,$index) = @_;
$self->lindex( $self->{name}, $index );
}
sub FETCHSIZE {
my ($self) = @_;
$self->llen( $self->{name} );
}
sub STORE {
my ($self,$index,$value) = @_;
$self->lset( $self->{name}, $index, $value );
}
sub STORESIZE {
my ($self,$count) = @_;
$self->ltrim( $self->{name}, 0, $count );
# if $count > $self->FETCHSIZE;
}
sub CLEAR {
my ($self) = @_;
$self->del( $self->{name} );
}
sub PUSH {
my $self = shift;
$self->rpush( $self->{name}, $_ ) foreach @_;
}
sub SHIFT {
my $self = shift;
$self->lpop( $self->{name} );
}
sub UNSHIFT {
my $self = shift;
$self->lpush( $self->{name}, $_ ) foreach @_;
}
sub SPLICE {
my $self = shift;
my $offset = shift;
my $length = shift;
$self->lrange( $self->{name}, $offset, $length );
# FIXME rest of @_ ?
}
sub EXTEND {
my ($self,$count) = @_;
$self->rpush( $self->{name}, '' ) foreach ( $self->FETCHSIZE .. ( $count - 1 ) );
}
sub DESTROY {
my $self = shift;
$self->quit;
}
1;

View File

@ -1,24 +0,0 @@
#!/usr/bin/perl
use warnings;
use strict;
use Benchmark qw/:all/;
use lib 'lib';
use Redis;
my $r = Redis->new;
my $i = 0;
timethese( 100000, {
'00_ping' => sub { $r->ping },
'10_set' => sub { $r->set( 'foo', $i++ ) },
'11_set_r' => sub { $r->set( 'bench-' . rand(), rand() ) },
'20_get' => sub { $r->get( 'foo' ) },
'21_get_r' => sub { $r->get( 'bench-' . rand() ) },
'30_incr' => sub { $r->incr( 'counter' ) },
'30_incr_r' => sub { $r->incr( 'bench-' . rand() ) },
'40_lpush' => sub { $r->lpush( 'mylist', 'bar' ) },
'40_lpush' => sub { $r->lpush( 'mylist', 'bar' ) },
'50_lpop' => sub { $r->lpop( 'mylist' ) },
});

View File

@ -1,9 +0,0 @@
#!perl -T
use Test::More tests => 1;
BEGIN {
use_ok( 'Redis' );
}
diag( "Testing Redis $Redis::VERSION, Perl $], $^X" );

View File

@ -1,189 +0,0 @@
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 106;
use Data::Dump qw/dump/;
use lib 'lib';
BEGIN {
use_ok( 'Redis' );
}
ok( my $o = Redis->new(), 'new' );
ok( $o->ping, 'ping' );
diag "Commands operating on string values";
ok( $o->set( foo => 'bar' ), 'set foo => bar' );
ok( ! $o->setnx( foo => 'bar' ), 'setnx foo => bar fails' );
cmp_ok( $o->get( 'foo' ), 'eq', 'bar', 'get foo = bar' );
ok( $o->set( foo => 'baz' ), 'set foo => baz' );
cmp_ok( $o->get( 'foo' ), 'eq', 'baz', 'get foo = baz' );
ok( $o->set( 'test-undef' => 42 ), 'set test-undef' );
ok( $o->set( 'test-undef' => undef ), 'set undef' );
ok( ! defined $o->get( 'test-undef' ), 'get undef' );
ok( $o->exists( 'test-undef' ), 'exists undef' );
$o->del('non-existant');
ok( ! $o->exists( 'non-existant' ), 'exists non-existant' );
ok( ! $o->get( 'non-existant' ), 'get non-existant' );
ok( $o->set('key-next' => 0), 'key-next = 0' );
my $key_next = 3;
ok( $o->set('key-left' => $key_next), 'key-left' );
is_deeply( [ $o->mget( 'foo', 'key-next', 'key-left' ) ], [ 'baz', 0, 3 ], 'mget' );
my @keys;
foreach my $id ( 0 .. $key_next ) {
my $key = 'key-' . $id;
push @keys, $key;
ok( $o->set( $key => $id ), "set $key" );
ok( $o->exists( $key ), "exists $key" );
cmp_ok( $o->get( $key ), 'eq', $id, "get $key" );
cmp_ok( $o->incr( 'key-next' ), '==', $id + 1, 'incr' );
cmp_ok( $o->decr( 'key-left' ), '==', $key_next - $id - 1, 'decr' );
}
cmp_ok( $o->get( 'key-next' ), '==', $key_next + 1, 'key-next' );
ok( $o->set('test-incrby', 0), 'test-incrby' );
ok( $o->set('test-decrby', 0), 'test-decry' );
foreach ( 1 .. 3 ) {
cmp_ok( $o->incrby('test-incrby', 3), '==', $_ * 3, 'incrby 3' );
cmp_ok( $o->decrby('test-decrby', 7), '==', -( $_ * 7 ), 'decrby 7' );
}
ok( $o->del( $_ ), "del $_" ) foreach map { "key-$_" } ( 'next', 'left' );
ok( ! $o->del('non-existing' ), 'del non-existing' );
cmp_ok( $o->type('foo'), 'eq', 'string', 'type' );
cmp_ok( $o->keys('key-*'), '==', $key_next + 1, 'key-*' );
is_deeply( [ $o->keys('key-*') ], [ @keys ], 'keys' );
ok( my $key = $o->randomkey, 'randomkey' );
ok( $o->rename( 'test-incrby', 'test-renamed' ), 'rename' );
ok( $o->exists( 'test-renamed' ), 'exists test-renamed' );
eval { $o->rename( 'test-decrby', 'test-renamed', 1 ) };
ok( $@, 'rename to existing key' );
ok( my $nr_keys = $o->dbsize, 'dbsize' );
diag "Commands operating on lists";
my $list = 'test-list';
$o->del($list) && diag "cleanup $list from last run";
ok( $o->rpush( $list => "r$_" ), 'rpush' ) foreach ( 1 .. 3 );
ok( $o->lpush( $list => "l$_" ), 'lpush' ) foreach ( 1 .. 2 );
cmp_ok( $o->type($list), 'eq', 'list', 'type' );
cmp_ok( $o->llen($list), '==', 5, 'llen' );
is_deeply( [ $o->lrange( $list, 0, 1 ) ], [ 'l2', 'l1' ], 'lrange' );
ok( $o->ltrim( $list, 1, 2 ), 'ltrim' );
cmp_ok( $o->llen($list), '==', 2, 'llen after ltrim' );
cmp_ok( $o->lindex( $list, 0 ), 'eq', 'l1', 'lindex' );
cmp_ok( $o->lindex( $list, 1 ), 'eq', 'r1', 'lindex' );
ok( $o->lset( $list, 0, 'foo' ), 'lset' );
cmp_ok( $o->lindex( $list, 0 ), 'eq', 'foo', 'verified' );
ok( $o->lrem( $list, 1, 'foo' ), 'lrem' );
cmp_ok( $o->llen( $list ), '==', 1, 'llen after lrem' );
cmp_ok( $o->lpop( $list ), 'eq', 'r1', 'lpop' );
ok( ! $o->rpop( $list ), 'rpop' );
diag "Commands operating on sets";
my $set = 'test-set';
$o->del($set);
ok( $o->sadd( $set, 'foo' ), 'sadd' );
ok( ! $o->sadd( $set, 'foo' ), 'sadd' );
cmp_ok( $o->scard( $set ), '==', 1, 'scard' );
ok( $o->sismember( $set, 'foo' ), 'sismember' );
cmp_ok( $o->type( $set ), 'eq', 'set', 'type is set' );
ok( $o->srem( $set, 'foo' ), 'srem' );
ok( ! $o->srem( $set, 'foo' ), 'srem again' );
cmp_ok( $o->scard( $set ), '==', 0, 'scard' );
$o->sadd( 'test-set1', $_ ) foreach ( 'foo', 'bar', 'baz' );
$o->sadd( 'test-set2', $_ ) foreach ( 'foo', 'baz', 'xxx' );
my $inter = [ 'baz', 'foo' ];
is_deeply( [ $o->sinter( 'test-set1', 'test-set2' ) ], $inter, 'siter' );
ok( $o->sinterstore( 'test-set-inter', 'test-set1', 'test-set2' ), 'sinterstore' );
cmp_ok( $o->scard( 'test-set-inter' ), '==', $#$inter + 1, 'cardinality of intersection' );
diag "Multiple databases handling commands";
ok( $o->select( 1 ), 'select' );
ok( $o->select( 0 ), 'select' );
ok( $o->move( 'foo', 1 ), 'move' );
ok( ! $o->exists( 'foo' ), 'gone' );
ok( $o->select( 1 ), 'select' );
ok( $o->exists( 'foo' ), 'exists' );
ok( $o->flushdb, 'flushdb' );
cmp_ok( $o->dbsize, '==', 0, 'empty' );
diag "Sorting";
ok( $o->lpush( 'test-sort', $_ ), "put $_" ) foreach ( 1 .. 4 );
cmp_ok( $o->llen( 'test-sort' ), '==', 4, 'llen' );
is_deeply( [ $o->sort( 'test-sort' ) ], [ 1,2,3,4 ], 'sort' );
is_deeply( [ $o->sort( 'test-sort DESC' ) ], [ 4,3,2,1 ], 'sort DESC' );
diag "Persistence control commands";
ok( $o->save, 'save' );
ok( $o->bgsave, 'bgsave' );
ok( $o->lastsave, 'lastsave' );
#ok( $o->shutdown, 'shutdown' );
diag "shutdown not tested";
diag "Remote server control commands";
ok( my $info = $o->info, 'info' );
diag dump( $info );
diag "Connection handling";
ok( $o->quit, 'quit' );

View File

@ -1,30 +0,0 @@
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 8;
use lib 'lib';
use Data::Dump qw/dump/;
BEGIN {
use_ok( 'Redis::List' );
}
my @a;
ok( my $o = tie( @a, 'Redis::List', 'test-redis-list' ), 'tie' );
isa_ok( $o, 'Redis::List' );
$o->CLEAR;
ok( ! @a, 'empty list' );
ok( @a = ( 'foo', 'bar', 'baz' ), '=' );
is_deeply( [ @a ], [ 'foo', 'bar', 'baz' ] );
ok( push( @a, 'push' ), 'push' );
is_deeply( [ @a ], [ 'foo', 'bar', 'baz', 'push' ] );
#diag dump( @a );

View File

@ -1,30 +0,0 @@
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 7;
use lib 'lib';
use Data::Dump qw/dump/;
BEGIN {
use_ok( 'Redis::Hash' );
}
ok( my $o = tie( my %h, 'Redis::Hash', 'test-redis-hash' ), 'tie' );
isa_ok( $o, 'Redis::Hash' );
$o->CLEAR();
ok( ! keys %h, 'empty' );
ok( %h = ( 'foo' => 42, 'bar' => 1, 'baz' => 99 ), '=' );
is_deeply( [ sort keys %h ], [ 'bar', 'baz', 'foo' ], 'keys' );
is_deeply( \%h, { bar => 1, baz => 99, foo => 42, }, 'structure' );
#diag dump( \%h );

View File

@ -1,18 +0,0 @@
use strict;
use warnings;
use Test::More;
# Ensure a recent version of Test::Pod::Coverage
my $min_tpc = 1.08;
eval "use Test::Pod::Coverage $min_tpc";
plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
if $@;
# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
# but older versions don't recognize some common documentation styles
my $min_pc = 0.18;
eval "use Pod::Coverage $min_pc";
plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
if $@;
all_pod_coverage_ok();

View File

@ -1,12 +0,0 @@
#!perl -T
use strict;
use warnings;
use Test::More;
# Ensure a recent version of Test::Pod
my $min_tp = 1.22;
eval "use Test::Pod $min_tp";
plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
all_pod_files_ok();

View File

@ -1,374 +0,0 @@
<?php
/*******************************************************************************
* Redis PHP Bindings - http://code.google.com/p/redis/
*
* Copyright 2009 Ludovico Magnocavallo
* Copyright 2009 Salvatore Sanfilippo (ported it to PHP5, fixed some bug)
* Released under the same license as Redis.
*
* Version: 0.1
*
* $Revision: 139 $
* $Date: 2009-03-15 22:59:40 +0100 (Dom, 15 Mar 2009) $
*
******************************************************************************/
class Redis {
public $server;
public $port;
private $_sock;
public function __construct($host='localhost', $port=6379) {
$this->host = $host;
$this->port = $port;
}
public function connect() {
if ($this->_sock) return;
if ($sock = fsockopen($this->host, $this->port, $errno, $errstr)) {
$this->_sock = $sock;
return;
}
$msg = "Cannot open socket to {$this->host}:{$this->port}";
if ($errno || $errmsg)
$msg .= "," . ($errno ? " error $errno" : "") .
($errmsg ? " $errmsg" : "");
trigger_error("$msg.", E_USER_ERROR);
}
public function disconnect() {
if ($this->_sock) @fclose($this->_sock);
$this->_sock = null;
}
public function ping() {
$this->connect();
$this->write("PING\r\n");
return $this->get_response();
}
public function do_echo($s) {
$this->connect();
$this->write("ECHO " . strlen($s) . "\r\n$s\r\n");
return $this->get_response();
}
public function set($name, $value, $preserve=false) {
$this->connect();
$this->write(
($preserve ? 'SETNX' : 'SET') .
" $name " . strlen($value) . "\r\n$value\r\n"
);
return $this->get_response();
}
public function get($name) {
$this->connect();
$this->write("GET $name\r\n");
return $this->get_response();
}
public function mget($keys) {
$this->connect();
$this->write("MGET ".implode(" ",$keys)."\r\n");
return $this->get_response();
}
public function incr($name, $amount=1) {
$this->connect();
if ($amount == 1)
$this->write("INCR $name\r\n");
else
$this->write("INCRBY $name $amount\r\n");
return $this->get_response();
}
public function decr($name, $amount=1) {
$this->connect();
if ($amount == 1)
$this->write("DECR $name\r\n");
else
$this->write("DECRBY $name $amount\r\n");
return $this->get_response();
}
public function exists($name) {
$this->connect();
$this->write("EXISTS $name\r\n");
return $this->get_response();
}
public function delete($name) {
$this->connect();
$this->write("DEL $name\r\n");
return $this->get_response();
}
public function keys($pattern) {
$this->connect();
$this->write("KEYS $pattern\r\n");
return explode(' ', $this->get_response());
}
public function randomkey() {
$this->connect();
$this->write("RANDOMKEY\r\n");
return $this->get_response();
}
public function rename($src, $dst) {
$this->connect();
$this->write("RENAME $src $dst\r\n");
return $this->get_response();
}
public function renamenx($src, $dst) {
$this->connect();
$this->write("RENAMENX $src $dst\r\n");
return $this->get_response();
}
public function expire($name, $time) {
$this->connect();
$this->write("EXPIRE $name $time\r\n");
return $this->get_response();
}
public function push($name, $value, $tail=true) {
// default is to append the element to the list
$this->connect();
$this->write(
($tail ? 'RPUSH' : 'LPUSH') .
" $name " . strlen($value) . "\r\n$value\r\n"
);
return $this->get_response();
}
public function lpush($name, $value) {
return $this->push($name, $value, false);
}
public function rpush($name, $value) {
return $this->push($name, $value, true);
}
public function ltrim($name, $start, $end) {
$this->connect();
$this->write("LTRIM $name $start $end\r\n");
return $this->get_response();
}
public function lindex($name, $index) {
$this->connect();
$this->write("LINDEX $name $index\r\n");
return $this->get_response();
}
public function pop($name, $tail=true) {
$this->connect();
$this->write(
($tail ? 'RPOP' : 'LPOP') .
" $name\r\n"
);
return $this->get_response();
}
public function lpop($name, $value) {
return $this->pop($name, $value, false);
}
public function rpop($name, $value) {
return $this->pop($name, $value, true);
}
public function llen($name) {
$this->connect();
$this->write("LLEN $name\r\n");
return $this->get_response();
}
public function lrange($name, $start, $end) {
$this->connect();
$this->write("LRANGE $name $start $end\r\n");
return $this->get_response();
}
public function sort($name, $query=false) {
$this->connect();
$this->write($query == false ? "SORT $name\r\n" : "SORT $name $query\r\n");
return $this->get_response();
}
public function lset($name, $value, $index) {
$this->connect();
$this->write("LSET $name $index " . strlen($value) . "\r\n$value\r\n");
return $this->get_response();
}
public function sadd($name, $value) {
$this->connect();
$this->write("SADD $name " . strlen($value) . "\r\n$value\r\n");
return $this->get_response();
}
public function srem($name, $value) {
$this->connect();
$this->write("SREM $name " . strlen($value) . "\r\n$value\r\n");
return $this->get_response();
}
public function sismember($name, $value) {
$this->connect();
$this->write("SISMEMBER $name " . strlen($value) . "\r\n$value\r\n");
return $this->get_response();
}
public function sinter($sets) {
$this->connect();
$this->write('SINTER ' . implode(' ', $sets) . "\r\n");
return $this->get_response();
}
public function smembers($name) {
$this->connect();
$this->write("SMEMBERS $name\r\n");
return $this->get_response();
}
public function scard($name) {
$this->connect();
$this->write("SCARD $name\r\n");
return $this->get_response();
}
public function select_db($name) {
$this->connect();
$this->write("SELECT $name\r\n");
return $this->get_response();
}
public function move($name, $db) {
$this->connect();
$this->write("MOVE $name $db\r\n");
return $this->get_response();
}
public function save($background=false) {
$this->connect();
$this->write(($background ? "BGSAVE\r\n" : "SAVE\r\n"));
return $this->get_response();
}
public function bgsave($background=false) {
return $this->save(true);
}
public function lastsave() {
$this->connect();
$this->write("LASTSAVE\r\n");
return $this->get_response();
}
public function flushdb($all=false) {
$this->connect();
$this->write($all ? "FLUSHALL\r\n" : "FLUSHDB\r\n");
return $this->get_response();
}
public function flushall() {
return $this->flush(true);
}
public function info() {
$this->connect();
$this->write("INFO\r\n");
$info = array();
$data =& $this->get_response();
foreach (explode("\r\n", $data) as $l) {
if (!$l)
continue;
list($k, $v) = explode(':', $l, 2);
$_v = strpos($v, '.') !== false ? (float)$v : (int)$v;
$info[$k] = (string)$_v == $v ? $_v : $v;
}
return $info;
}
private function write($s) {
while ($s) {
$i = fwrite($this->_sock, $s);
if ($i == 0) // || $i == strlen($s))
break;
$s = substr($s, $i);
}
}
private function read($len=1024) {
if ($s = fgets($this->_sock))
return $s;
$this->disconnect();
trigger_error("Cannot read from socket.", E_USER_ERROR);
}
private function get_response() {
$data = trim($this->read());
$c = $data[0];
$data = substr($data, 1);
switch ($c) {
case '-':
trigger_error($data, E_USER_ERROR);
break;
case '+':
return $data;
case ':':
$i = strpos($data, '.') !== false ? (int)$data : (float)$data;
if ((string)$i != $data)
trigger_error("Cannot convert data '$c$data' to integer", E_USER_ERROR);
return $i;
case '$':
return $this->get_bulk_reply($c . $data);
case '*':
$num = (int)$data;
if ((string)$num != $data)
trigger_error("Cannot convert multi-response header '$data' to integer", E_USER_ERROR);
$result = array();
for ($i=0; $i<$num; $i++)
$result[] =& $this->get_response();
return $result;
default:
trigger_error("Invalid reply type byte: '$c'");
}
}
private function get_bulk_reply($data=null) {
if ($data === null)
$data = trim($this->read());
if ($data == '$-1')
return null;
$c = $data[0];
$data = substr($data, 1);
$bulklen = (int)$data;
if ((string)$bulklen != $data)
trigger_error("Cannot convert bulk read header '$c$data' to integer", E_USER_ERROR);
if ($c != '$')
trigger_error("Unkown response prefix for '$c$data'", E_USER_ERROR);
$buffer = '';
while ($bulklen) {
$data = fread($this->_sock,$bulklen);
$bulklen -= strlen($data);
$buffer .= $data;
}
$crlf = fread($this->_sock,2);
return $buffer;
}
}
/*
$r = new Redis();
var_dump($r->set("foo","bar"));
var_dump($r->get("foo"));
var_dump($r->info());
*/
?>

View File

@ -1,87 +0,0 @@
<?php
// poor man's tests
require_once('redis.php');
$r =& new Redis('localhost');
$r->connect();
$r->select_db(9);
$r->flushdb();
echo "<pre>\n";
echo $r->ping() . "\n";
echo $r->do_echo('ECHO test') . "\n";
echo "SET aaa " . $r->set('aaa', 'bbb') . "\n";
echo "SETNX aaa " . $r->set('aaa', 'ccc', true) . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "INCR aaa " . $r->incr('aaa') . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "INCRBY aaa 3 " . $r->incr('aaa', 2) . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "DECR aaa " . $r->decr('aaa') . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "DECRBY aaa 2 " . $r->decr('aaa', 2) . "\n";
echo "GET aaa " . $r->get('aaa') . "\n";
echo "EXISTS aaa " . $r->exists('aaa') . "\n";
echo "EXISTS fsfjslfjkls " . $r->exists('fsfjslfjkls') . "\n";
echo "DELETE aaa " . $r->delete('aaa') . "\n";
echo "EXISTS aaa " . $r->exists('aaa') . "\n";
echo 'SET a1 a2 a3' . $r->set('a1', 'a') . $r->set('a2', 'b') . $r->set('a3', 'c') . "\n";
echo 'KEYS a* ' . print_r($r->keys('a*'), true) . "\n";
echo 'RANDOMKEY ' . $r->randomkey('a*') . "\n";
echo 'RENAME a1 a0 ' . $r->rename('a1', 'a0') . "\n";
echo 'RENAMENX a0 a2 ' . $r->renamenx('a0', 'a2') . "\n";
echo 'RENAMENX a0 a1 ' . $r->renamenx('a0', 'a1') . "\n";
echo 'LPUSH a0 aaa ' . $r->push('a0', 'aaa') . "\n";
echo 'LPUSH a0 bbb ' . $r->push('a0', 'bbb') . "\n";
echo 'RPUSH a0 ccc ' . $r->push('a0', 'ccc', false) . "\n";
echo 'LLEN a0 ' . $r->llen('a0') . "\n";
echo 'LRANGE sdkjhfskdjfh 0 100 ' . print_r($r->lrange('sdkjhfskdjfh', 0, 100), true) . "\n";
echo 'LRANGE a0 0 0 ' . print_r($r->lrange('a0', 0, 0), true) . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'LTRIM a0 0 1 ' . $r->ltrim('a0', 0, 1) . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'LINDEX a0 0 ' . $r->lindex('a0', 0) . "\n";
echo 'LPUSH a0 bbb ' . $r->push('a0', 'bbb') . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'RPOP a0 ' . $r->pop('a0') . "\n";
echo 'LPOP a0 ' . $r->pop('a0', false) . "\n";
echo 'LSET a0 ccc 0 ' . $r->lset('a0', 'ccc', 0) . "\n";
echo 'LRANGE a0 0 100 ' . print_r($r->lrange('a0', 0, 100), true) . "\n";
echo 'SADD s0 aaa ' . $r->sadd('s0', 'aaa') . "\n";
echo 'SADD s0 aaa ' . $r->sadd('s0', 'aaa') . "\n";
echo 'SADD s0 bbb ' . $r->sadd('s0', 'bbb') . "\n";
echo 'SREM s0 bbb ' . $r->srem('s0', 'bbb') . "\n";
echo 'SISMEMBER s0 aaa ' . $r->sismember('s0', 'aaa') . "\n";
echo 'SISMEMBER s0 bbb ' . $r->sismember('s0', 'bbb') . "\n";
echo 'SADD s0 bbb ' . $r->sadd('s0', 'bbb') . "\n";
echo 'SADD s1 bbb ' . $r->sadd('s1', 'bbb') . "\n";
echo 'SADD s1 aaa ' . $r->sadd('s1', 'aaa') . "\n";
echo 'SINTER s0 s1 ' . print_r($r->sinter(array('s0', 's1')), true) . "\n";
echo 'SREM s0 bbb ' . $r->srem('s0', 'bbb') . "\n";
echo 'SINTER s0 s1 ' . print_r($r->sinter(array('s0', 's1')), true) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 8 ' . $r->select_db(8) . "\n";
echo 'EXISTS s1 ' . $r->exists('s1') . "\n";
if ($r->exists('s1'))
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 9 ' . $r->select_db(9) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'MOVE s1 8 ' . $r->move('s1', 8) . "\n";
echo 'EXISTS s1 ' . $r->exists('s1') . "\n";
if ($r->exists('s1'))
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 8 ' . $r->select_db(8) . "\n";
echo 'SMEMBERS s1 ' . print_r($r->smembers('s1'), true) . "\n";
echo 'SELECT 9 ' . $r->select_db(9) . "\n";
echo 'SAVE ' . $r->save() . "\n";
echo 'BGSAVE ' . $r->save(true) . "\n";
echo 'LASTSAVE ' . $r->lastsave() . "\n";
echo 'INFO ' . print_r($r->info()) . "\n";
echo "</pre>\n";
?>

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
nohup.out
redis/*
rdsrv
pkg/*
coverage/*
.idea

View File

@ -1,20 +0,0 @@
Copyright (c) 2009 Ezra Zygmuntowicz
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,34 +0,0 @@
# redis-rb
A ruby client library for the redis key value storage system.
## Information about redis
Redis is a key value store with some interesting features:
1. It's fast.
2. Keys are strings but values can have types of "NONE", "STRING", "LIST", or "SET". List's can be atomically push'd, pop'd, lpush'd, lpop'd and indexed. This allows you to store things like lists of comments under one key while retaining the ability to append comments without reading and putting back the whole list.
See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for more information.
## Dependencies
1. rspec -
sudo gem install rspec
2. redis -
rake redis:install
2. dtach -
rake dtach:install
3. git - git is the new black.
## Setup
Use the tasks mentioned above (in Dependencies) to get your machine setup.
## Examples
Check the examples/ directory. *Note* you need to have redis-server running first.

View File

@ -1,62 +0,0 @@
require 'rubygems'
require 'rake/gempackagetask'
require 'rubygems/specification'
require 'date'
require 'spec/rake/spectask'
require 'tasks/redis.tasks'
GEM = 'redis'
GEM_NAME = 'redis'
GEM_VERSION = '0.1'
AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark', 'Brian McKinney', 'Salvatore Sanfilippo', 'Luca Guidi']
EMAIL = "ez@engineyard.com"
HOMEPAGE = "http://github.com/ezmobius/redis-rb"
SUMMARY = "Ruby client library for redis key value storage server"
spec = Gem::Specification.new do |s|
s.name = GEM
s.version = GEM_VERSION
s.platform = Gem::Platform::RUBY
s.has_rdoc = true
s.extra_rdoc_files = ["LICENSE"]
s.summary = SUMMARY
s.description = s.summary
s.authors = AUTHORS
s.email = EMAIL
s.homepage = HOMEPAGE
s.add_dependency "rspec"
s.require_path = 'lib'
s.autorequire = GEM
s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,tasks,spec}/**/*")
end
task :default => :spec
desc "Run specs"
Spec::Rake::SpecTask.new do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.spec_opts = %w(-fs --color)
end
Rake::GemPackageTask.new(spec) do |pkg|
pkg.gem_spec = spec
end
desc "install the gem locally"
task :install => [:package] do
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
end
desc "create a gemspec file"
task :make_spec do
File.open("#{GEM}.gemspec", "w") do |file|
file.puts spec.to_ruby
end
end
desc "Run all examples with RCov"
Spec::Rake::SpecTask.new(:rcov) do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.rcov = true
end

View File

@ -1,44 +0,0 @@
require 'benchmark'
$:.push File.join(File.dirname(__FILE__), 'lib')
require 'redis'
times = 20000
@r = Redis.new#(:debug => true)
@r['foo'] = "The first line we sent to the server is some text"
Benchmark.bmbm do |x|
x.report("set") do
20000.times do |i|
@r["set#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]
end
end
x.report("set (pipelined)") do
@r.pipelined do |pipeline|
20000.times do |i|
pipeline["set_pipelined#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]
end
end
end
x.report("push+trim") do
20000.times do |i|
@r.push_head "push_trim#{i}", i
@r.list_trim "push_trim#{i}", 0, 30
end
end
x.report("push+trim (pipelined)") do
@r.pipelined do |pipeline|
20000.times do |i|
pipeline.push_head "push_trim_pipelined#{i}", i
pipeline.list_trim "push_trim_pipelined#{i}", 0, 30
end
end
end
end
@r.keys('*').each do |k|
@r.delete k
end

View File

@ -1,24 +0,0 @@
require 'fileutils'
def run_in_background(command)
fork { system command }
end
def with_all_segments(&block)
0.upto(9) do |segment_number|
block_size = 100000
start_index = segment_number * block_size
end_index = start_index + block_size - 1
block.call(start_index, end_index)
end
end
#with_all_segments do |start_index, end_index|
# puts "Initializing keys from #{start_index} to #{end_index}"
# system "ruby worker.rb initialize #{start_index} #{end_index} 0"
#end
with_all_segments do |start_index, end_index|
run_in_background "ruby worker.rb write #{start_index} #{end_index} 10"
run_in_background "ruby worker.rb read #{start_index} #{end_index} 1"
end

View File

@ -1,71 +0,0 @@
BENCHMARK_ROOT = File.dirname(__FILE__)
REDIS_ROOT = File.join(BENCHMARK_ROOT, "..", "lib")
$: << REDIS_ROOT
require 'redis'
require 'benchmark'
def show_usage
puts <<-EOL
Usage: worker.rb [read:write] <start_index> <end_index> <sleep_msec>
EOL
end
def shift_from_argv
value = ARGV.shift
unless value
show_usage
exit -1
end
value
end
operation = shift_from_argv.to_sym
start_index = shift_from_argv.to_i
end_index = shift_from_argv.to_i
sleep_msec = shift_from_argv.to_i
sleep_duration = sleep_msec/1000.0
redis = Redis.new
case operation
when :initialize
start_index.upto(end_index) do |i|
redis[i] = 0
end
when :clear
start_index.upto(end_index) do |i|
redis.delete(i)
end
when :read, :write
puts "Starting to #{operation} at segment #{end_index + 1}"
loop do
t1 = Time.now
start_index.upto(end_index) do |i|
case operation
when :read
redis.get(i)
when :write
redis.incr(i)
else
raise "Unknown operation: #{operation}"
end
sleep sleep_duration
end
t2 = Time.now
requests_processed = end_index - start_index
time = t2 - t1
puts "#{t2.strftime("%H:%M")} [segment #{end_index + 1}] : Processed #{requests_processed} requests in #{time} seconds - #{(requests_processed/time).round} requests/sec"
end
else
raise "Unknown operation: #{operation}"
end

View File

@ -1,33 +0,0 @@
require 'fileutils'
class RedisCluster
def initialize(opts={})
opts = {:port => 6379, :host => 'localhost', :basedir => "#{Dir.pwd}/rdsrv" }.merge(opts)
FileUtils.mkdir_p opts[:basedir]
opts[:size].times do |i|
port = opts[:port] + i
FileUtils.mkdir_p "#{opts[:basedir]}/#{port}"
File.open("#{opts[:basedir]}/#{port}.conf", 'w'){|f| f.write(make_config(port, "#{opts[:basedir]}/#{port}", "#{opts[:basedir]}/#{port}.log"))}
system(%Q{#{File.join(File.expand_path(File.dirname(__FILE__)), "../redis/redis-server #{opts[:basedir]}/#{port}.conf &" )}})
end
end
def make_config(port=6379, data=port, logfile='stdout', loglevel='debug')
config = %Q{
timeout 300
save 900 1
save 300 10
save 60 10000
dir #{data}
loglevel #{loglevel}
logfile #{logfile}
databases 16
port #{port}
}
end
end
RedisCluster.new :size => 4

View File

@ -1,16 +0,0 @@
require 'rubygems'
require 'redis'
r = Redis.new
r.delete('foo')
puts
p'set foo to "bar"'
r['foo'] = 'bar'
puts
p 'value of foo'
p r['foo']

View File

@ -1,18 +0,0 @@
require 'rubygems'
require 'redis'
r = Redis.new
puts
p 'incr'
r.delete 'counter'
p r.incr('counter')
p r.incr('counter')
p r.incr('counter')
puts
p 'decr'
p r.decr('counter')
p r.decr('counter')
p r.decr('counter')

View File

@ -1,26 +0,0 @@
require 'rubygems'
require 'redis'
r = Redis.new
r.delete 'logs'
puts
p "pushing log messages into a LIST"
r.push_tail 'logs', 'some log message'
r.push_tail 'logs', 'another log message'
r.push_tail 'logs', 'yet another log message'
r.push_tail 'logs', 'also another log message'
puts
p 'contents of logs LIST'
p r.list_range('logs', 0, -1)
puts
p 'Trim logs LIST to last 2 elements(easy circular buffer)'
r.list_trim('logs', -2, -1)
p r.list_range('logs', 0, -1)

View File

@ -1,36 +0,0 @@
require 'rubygems'
require 'redis'
r = Redis.new
r.delete 'foo-tags'
r.delete 'bar-tags'
puts
p "create a set of tags on foo-tags"
r.set_add 'foo-tags', 'one'
r.set_add 'foo-tags', 'two'
r.set_add 'foo-tags', 'three'
puts
p "create a set of tags on bar-tags"
r.set_add 'bar-tags', 'three'
r.set_add 'bar-tags', 'four'
r.set_add 'bar-tags', 'five'
puts
p 'foo-tags'
p r.set_members('foo-tags')
puts
p 'bar-tags'
p r.set_members('bar-tags')
puts
p 'intersection of foo-tags and bar-tags'
p r.set_intersect('foo-tags', 'bar-tags')

View File

@ -1,124 +0,0 @@
require 'redis'
require 'hash_ring'
class DistRedis
attr_reader :ring
def initialize(opts={})
hosts = []
db = opts[:db] || nil
timeout = opts[:timeout] || nil
raise Error, "No hosts given" unless opts[:hosts]
opts[:hosts].each do |h|
host, port = h.split(':')
hosts << Redis.new(:host => host, :port => port, :db => db, :timeout => timeout)
end
@ring = HashRing.new hosts
end
def node_for_key(key)
key = $1 if key =~ /\{(.*)?\}/
@ring.get_node(key)
end
def add_server(server)
server, port = server.split(':')
@ring.add_node Redis.new(:host => server, :port => port)
end
def method_missing(sym, *args, &blk)
if redis = node_for_key(args.first.to_s)
redis.send sym, *args, &blk
else
super
end
end
def keys(glob)
@ring.nodes.map do |red|
red.keys(glob)
end
end
def save
on_each_node :save
end
def bgsave
on_each_node :bgsave
end
def quit
on_each_node :quit
end
def flush_all
on_each_node :flush_all
end
alias_method :flushall, :flush_all
def flush_db
on_each_node :flush_db
end
alias_method :flushdb, :flush_db
def delete_cloud!
@ring.nodes.each do |red|
red.keys("*").each do |key|
red.delete key
end
end
end
def on_each_node(command, *args)
@ring.nodes.each do |red|
red.send(command, *args)
end
end
end
if __FILE__ == $0
r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
r['urmom'] = 'urmom'
r['urdad'] = 'urdad'
r['urmom1'] = 'urmom1'
r['urdad1'] = 'urdad1'
r['urmom2'] = 'urmom2'
r['urdad2'] = 'urdad2'
r['urmom3'] = 'urmom3'
r['urdad3'] = 'urdad3'
p r['urmom']
p r['urdad']
p r['urmom1']
p r['urdad1']
p r['urmom2']
p r['urdad2']
p r['urmom3']
p r['urdad3']
r.push_tail 'listor', 'foo1'
r.push_tail 'listor', 'foo2'
r.push_tail 'listor', 'foo3'
r.push_tail 'listor', 'foo4'
r.push_tail 'listor', 'foo5'
p r.pop_tail('listor')
p r.pop_tail('listor')
p r.pop_tail('listor')
p r.pop_tail('listor')
p r.pop_tail('listor')
puts "key distribution:"
r.ring.nodes.each do |red|
p [red.port, red.keys("*")]
end
r.delete_cloud!
p r.keys('*')
end

View File

@ -1,128 +0,0 @@
require 'zlib'
class HashRing
POINTS_PER_SERVER = 160 # this is the default in libmemcached
attr_reader :ring, :sorted_keys, :replicas, :nodes
# nodes is a list of objects that have a proper to_s representation.
# replicas indicates how many virtual points should be used pr. node,
# replicas are required to improve the distribution.
def initialize(nodes=[], replicas=POINTS_PER_SERVER)
@replicas = replicas
@ring = {}
@nodes = []
@sorted_keys = []
nodes.each do |node|
add_node(node)
end
end
# Adds a `node` to the hash ring (including a number of replicas).
def add_node(node)
@nodes << node
@replicas.times do |i|
key = Zlib.crc32("#{node}:#{i}")
@ring[key] = node
@sorted_keys << key
end
@sorted_keys.sort!
end
def remove_node(node)
@nodes.reject!{|n| n.to_s == node.to_s}
@replicas.times do |i|
key = Zlib.crc32("#{node}:#{i}")
@ring.delete(key)
@sorted_keys.reject! {|k| k == key}
end
end
# get the node in the hash ring for this key
def get_node(key)
get_node_pos(key)[0]
end
def get_node_pos(key)
return [nil,nil] if @ring.size == 0
crc = Zlib.crc32(key)
idx = HashRing.binary_search(@sorted_keys, crc)
return [@ring[@sorted_keys[idx]], idx]
end
def iter_nodes(key)
return [nil,nil] if @ring.size == 0
node, pos = get_node_pos(key)
@sorted_keys[pos..-1].each do |k|
yield @ring[k]
end
end
class << self
# gem install RubyInline to use this code
# Native extension to perform the binary search within the hashring.
# There's a pure ruby version below so this is purely optional
# for performance. In testing 20k gets and sets, the native
# binary search shaved about 12% off the runtime (9sec -> 8sec).
begin
require 'inline'
inline do |builder|
builder.c <<-EOM
int binary_search(VALUE ary, unsigned int r) {
int upper = RARRAY_LEN(ary) - 1;
int lower = 0;
int idx = 0;
while (lower <= upper) {
idx = (lower + upper) / 2;
VALUE continuumValue = RARRAY_PTR(ary)[idx];
unsigned int l = NUM2UINT(continuumValue);
if (l == r) {
return idx;
}
else if (l > r) {
upper = idx - 1;
}
else {
lower = idx + 1;
}
}
return upper;
}
EOM
end
rescue Exception => e
# Find the closest index in HashRing with value <= the given value
def binary_search(ary, value, &block)
upper = ary.size - 1
lower = 0
idx = 0
while(lower <= upper) do
idx = (lower + upper) / 2
comp = ary[idx] <=> value
if comp == 0
return idx
elsif comp > 0
upper = idx - 1
else
lower = idx + 1
end
end
return upper
end
end
end
end
# ring = HashRing.new ['server1', 'server2', 'server3']
# p ring
# #
# p ring.get_node "kjhjkjlkjlkkh"
#

View File

@ -1,22 +0,0 @@
require "redis"
class Redis
class Pipeline < Redis
BUFFER_SIZE = 50_000
def initialize(redis)
@redis = redis
@commands = []
end
def call_command(command)
@commands << command
end
def execute
@redis.call_command(@commands)
@commands.clear
end
end
end

View File

@ -1,316 +0,0 @@
require 'socket'
require File.join(File.dirname(__FILE__),'pipeline')
begin
if RUBY_VERSION >= '1.9'
require 'timeout'
RedisTimer = Timeout
else
require 'system_timer'
RedisTimer = SystemTimer
end
rescue LoadError
RedisTimer = nil
end
class Redis
OK = "OK".freeze
MINUS = "-".freeze
PLUS = "+".freeze
COLON = ":".freeze
DOLLAR = "$".freeze
ASTERISK = "*".freeze
BULK_COMMANDS = {
"set" => true,
"setnx" => true,
"rpush" => true,
"lpush" => true,
"lset" => true,
"lrem" => true,
"sadd" => true,
"srem" => true,
"sismember" => true,
"echo" => true,
"getset" => true,
"smove" => true
}
BOOLEAN_PROCESSOR = lambda{|r| r == 1 }
REPLY_PROCESSOR = {
"exists" => BOOLEAN_PROCESSOR,
"sismember" => BOOLEAN_PROCESSOR,
"sadd" => BOOLEAN_PROCESSOR,
"srem" => BOOLEAN_PROCESSOR,
"smove" => BOOLEAN_PROCESSOR,
"move" => BOOLEAN_PROCESSOR,
"setnx" => BOOLEAN_PROCESSOR,
"del" => BOOLEAN_PROCESSOR,
"renamenx" => BOOLEAN_PROCESSOR,
"expire" => BOOLEAN_PROCESSOR,
"keys" => lambda{|r| r.split(" ")},
"info" => lambda{|r|
info = {}
r.each_line {|kv|
k,v = kv.split(":",2).map{|x| x.chomp}
info[k.to_sym] = v
}
info
}
}
ALIASES = {
"flush_db" => "flushdb",
"flush_all" => "flushall",
"last_save" => "lastsave",
"key?" => "exists",
"delete" => "del",
"randkey" => "randomkey",
"list_length" => "llen",
"push_tail" => "rpush",
"push_head" => "lpush",
"pop_tail" => "rpop",
"pop_head" => "lpop",
"list_set" => "lset",
"list_range" => "lrange",
"list_trim" => "ltrim",
"list_index" => "lindex",
"list_rm" => "lrem",
"set_add" => "sadd",
"set_delete" => "srem",
"set_count" => "scard",
"set_member?" => "sismember",
"set_members" => "smembers",
"set_intersect" => "sinter",
"set_intersect_store" => "sinterstore",
"set_inter_store" => "sinterstore",
"set_union" => "sunion",
"set_union_store" => "sunionstore",
"set_diff" => "sdiff",
"set_diff_store" => "sdiffstore",
"set_move" => "smove",
"set_unless_exists" => "setnx",
"rename_unless_exists" => "renamenx",
"type?" => "type"
}
DISABLED_COMMANDS = {
"monitor" => true,
"sync" => true
}
def initialize(options = {})
@host = options[:host] || '127.0.0.1'
@port = (options[:port] || 6379).to_i
@db = (options[:db] || 0).to_i
@timeout = (options[:timeout] || 5).to_i
@password = options[:password]
@logger = options[:logger]
@logger.info { self.to_s } if @logger
connect_to_server
end
def to_s
"Redis Client connected to #{server} against DB #{@db}"
end
def server
"#{@host}:#{@port}"
end
def connect_to_server
@sock = connect_to(@host, @port, @timeout == 0 ? nil : @timeout)
call_command(["auth",@password]) if @password
call_command(["select",@db]) unless @db == 0
end
def connect_to(host, port, timeout=nil)
# We support connect() timeout only if system_timer is availabe
# or if we are running against Ruby >= 1.9
# Timeout reading from the socket instead will be supported anyway.
if @timeout != 0 and RedisTimer
begin
sock = TCPSocket.new(host, port)
rescue Timeout::Error
@sock = nil
raise Timeout::Error, "Timeout connecting to the server"
end
else
sock = TCPSocket.new(host, port)
end
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
# If the timeout is set we set the low level socket options in order
# to make sure a blocking read will return after the specified number
# of seconds. This hack is from memcached ruby client.
if timeout
secs = Integer(timeout)
usecs = Integer((timeout - secs) * 1_000_000)
optval = [secs, usecs].pack("l_2")
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
end
sock
end
def method_missing(*argv)
call_command(argv)
end
def call_command(argv)
@logger.debug { argv.inspect } if @logger
# this wrapper to raw_call_command handle reconnection on socket
# error. We try to reconnect just one time, otherwise let the error
# araise.
connect_to_server if !@sock
begin
raw_call_command(argv.dup)
rescue Errno::ECONNRESET, Errno::EPIPE
@sock.close
@sock = nil
connect_to_server
raw_call_command(argv.dup)
end
end
def raw_call_command(argvp)
pipeline = argvp[0].is_a?(Array)
unless pipeline
argvv = [argvp]
else
argvv = argvp
end
command = ''
argvv.each do |argv|
bulk = nil
argv[0] = argv[0].to_s.downcase
argv[0] = ALIASES[argv[0]] if ALIASES[argv[0]]
raise "#{argv[0]} command is disabled" if DISABLED_COMMANDS[argv[0]]
if BULK_COMMANDS[argv[0]] and argv.length > 1
bulk = argv[-1].to_s
argv[-1] = bulk.respond_to?(:bytesize) ? bulk.bytesize : bulk.size
end
command << "#{argv.join(' ')}\r\n"
command << "#{bulk}\r\n" if bulk
end
@sock.write(command)
results = argvv.map do |argv|
processor = REPLY_PROCESSOR[argv[0]]
processor ? processor.call(read_reply) : read_reply
end
return pipeline ? results : results[0]
end
def select(*args)
raise "SELECT not allowed, use the :db option when creating the object"
end
def [](key)
self.get(key)
end
def []=(key,value)
set(key,value)
end
def set(key, value, expiry=nil)
s = call_command([:set, key, value]) == OK
expire(key, expiry) if s && expiry
s
end
def sort(key, options = {})
cmd = ["SORT"]
cmd << key
cmd << "BY #{options[:by]}" if options[:by]
cmd << "GET #{[options[:get]].flatten * ' GET '}" if options[:get]
cmd << "#{options[:order]}" if options[:order]
cmd << "LIMIT #{options[:limit].join(' ')}" if options[:limit]
call_command(cmd)
end
def incr(key, increment = nil)
call_command(increment ? ["incrby",key,increment] : ["incr",key])
end
def decr(key,decrement = nil)
call_command(decrement ? ["decrby",key,decrement] : ["decr",key])
end
# Similar to memcache.rb's #get_multi, returns a hash mapping
# keys to values.
def mapped_mget(*keys)
mget(*keys).inject({}) do |hash, value|
key = keys.shift
value.nil? ? hash : hash.merge(key => value)
end
end
# Ruby defines a now deprecated type method so we need to override it here
# since it will never hit method_missing
def type(key)
call_command(['type', key])
end
def quit
call_command(['quit'])
rescue Errno::ECONNRESET
end
def pipelined(&block)
pipeline = Pipeline.new self
yield pipeline
pipeline.execute
end
def read_reply
# We read the first byte using read() mainly because gets() is
# immune to raw socket timeouts.
begin
rtype = @sock.read(1)
rescue Errno::EAGAIN
# We want to make sure it reconnects on the next command after the
# timeout. Otherwise the server may reply in the meantime leaving
# the protocol in a desync status.
@sock = nil
raise Errno::EAGAIN, "Timeout reading from the socket"
end
raise Errno::ECONNRESET,"Connection lost" if !rtype
line = @sock.gets
case rtype
when MINUS
raise MINUS + line.strip
when PLUS
line.strip
when COLON
line.to_i
when DOLLAR
bulklen = line.to_i
return nil if bulklen == -1
data = @sock.read(bulklen)
@sock.read(2) # CRLF
data
when ASTERISK
objects = line.to_i
return nil if bulklen == -1
res = []
objects.times {
res << read_reply
}
res
else
raise "Protocol error, got '#{rtype}' as initial reply byte"
end
end
end

View File

@ -1,22 +0,0 @@
require 'rubygems'
require 'ruby-prof'
require "#{File.dirname(__FILE__)}/lib/redis"
mode = ARGV.shift || 'process_time'
n = (ARGV.shift || 200).to_i
r = Redis.new
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
RubyProf.start
n.times do |i|
key = "foo#{i}"
r[key] = key * 10
r[key]
end
results = RubyProf.stop
File.open("profile.#{mode}", 'w') do |out|
RubyProf::CallTreePrinter.new(results).print(out)
end

View File

@ -1,30 +0,0 @@
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = %q{redis}
s.version = "0.1"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Ezra Zygmuntowicz", "Taylor Weibley", "Matthew Clark", "Brian McKinney", "Salvatore Sanfilippo", "Luca Guidi"]
# s.autorequire = %q{redis-rb}
s.date = %q{2009-06-23}
s.description = %q{Ruby client library for redis key value storage server}
s.email = %q{ez@engineyard.com}
s.extra_rdoc_files = ["LICENSE"]
s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/dist_redis.rb", "lib/hash_ring.rb", "lib/pipeline.rb", "lib/redis.rb", "spec/redis_spec.rb", "spec/spec_helper.rb"]
s.has_rdoc = true
s.homepage = %q{http://github.com/ezmobius/redis-rb}
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.1}
s.summary = %q{Ruby client library for redis key value storage server}
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 2
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
else
end
else
end
end

View File

@ -1,478 +0,0 @@
require File.dirname(__FILE__) + '/spec_helper'
require 'logger'
class Foo
attr_accessor :bar
def initialize(bar)
@bar = bar
end
def ==(other)
@bar == other.bar
end
end
describe "redis" do
before(:all) do
# use database 15 for testing so we dont accidentally step on you real data
@r = Redis.new :db => 15
end
before(:each) do
@r['foo'] = 'bar'
end
after(:each) do
@r.keys('*').each {|k| @r.del k}
end
after(:all) do
@r.quit
end
it "should be able connect without a timeout" do
lambda { Redis.new :timeout => 0 }.should_not raise_error
end
it "should be able to provide a logger" do
log = StringIO.new
r = Redis.new :db => 15, :logger => Logger.new(log)
r.ping
log.string.should include("ping")
end
it "should be able to PING" do
@r.ping.should == 'PONG'
end
it "should be able to GET a key" do
@r['foo'].should == 'bar'
end
it "should be able to SET a key" do
@r['foo'] = 'nik'
@r['foo'].should == 'nik'
end
it "should properly handle trailing newline characters" do
@r['foo'] = "bar\n"
@r['foo'].should == "bar\n"
end
it "should store and retrieve all possible characters at the beginning and the end of a string" do
(0..255).each do |char_idx|
string = "#{char_idx.chr}---#{char_idx.chr}"
@r['foo'] = string
@r['foo'].should == string
end
end
it "should be able to SET a key with an expiry" do
@r.set('foo', 'bar', 1)
@r['foo'].should == 'bar'
sleep 2
@r['foo'].should == nil
end
it "should be able to return a TTL for a key" do
@r.set('foo', 'bar', 1)
@r.ttl('foo').should == 1
end
it "should be able to SETNX" do
@r['foo'] = 'nik'
@r['foo'].should == 'nik'
@r.setnx 'foo', 'bar'
@r['foo'].should == 'nik'
end
#
it "should be able to GETSET" do
@r.getset('foo', 'baz').should == 'bar'
@r['foo'].should == 'baz'
end
#
it "should be able to INCR a key" do
@r.del('counter')
@r.incr('counter').should == 1
@r.incr('counter').should == 2
@r.incr('counter').should == 3
end
#
it "should be able to INCRBY a key" do
@r.del('counter')
@r.incrby('counter', 1).should == 1
@r.incrby('counter', 2).should == 3
@r.incrby('counter', 3).should == 6
end
#
it "should be able to DECR a key" do
@r.del('counter')
@r.incr('counter').should == 1
@r.incr('counter').should == 2
@r.incr('counter').should == 3
@r.decr('counter').should == 2
@r.decr('counter', 2).should == 0
end
#
it "should be able to RANDKEY" do
@r.randkey.should_not be_nil
end
#
it "should be able to RENAME a key" do
@r.del 'foo'
@r.del'bar'
@r['foo'] = 'hi'
@r.rename 'foo', 'bar'
@r['bar'].should == 'hi'
end
#
it "should be able to RENAMENX a key" do
@r.del 'foo'
@r.del 'bar'
@r['foo'] = 'hi'
@r['bar'] = 'ohai'
@r.renamenx 'foo', 'bar'
@r['bar'].should == 'ohai'
end
#
it "should be able to get DBSIZE of the database" do
@r.delete 'foo'
dbsize_without_foo = @r.dbsize
@r['foo'] = 0
dbsize_with_foo = @r.dbsize
dbsize_with_foo.should == dbsize_without_foo + 1
end
#
it "should be able to EXPIRE a key" do
@r['foo'] = 'bar'
@r.expire 'foo', 1
@r['foo'].should == "bar"
sleep 2
@r['foo'].should == nil
end
#
it "should be able to EXISTS" do
@r['foo'] = 'nik'
@r.exists('foo').should be_true
@r.del 'foo'
@r.exists('foo').should be_false
end
#
it "should be able to KEYS" do
@r.keys("f*").each { |key| @r.del key }
@r['f'] = 'nik'
@r['fo'] = 'nak'
@r['foo'] = 'qux'
@r.keys("f*").sort.should == ['f','fo', 'foo'].sort
end
#
it "should be able to return a random key (RANDOMKEY)" do
3.times { @r.exists(@r.randomkey).should be_true }
end
#
it "should be able to check the TYPE of a key" do
@r['foo'] = 'nik'
@r.type('foo').should == "string"
@r.del 'foo'
@r.type('foo').should == "none"
end
#
it "should be able to push to the head of a list (LPUSH)" do
@r.lpush "list", 'hello'
@r.lpush "list", 42
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lpop('list').should == '42'
end
#
it "should be able to push to the tail of a list (RPUSH)" do
@r.rpush "list", 'hello'
@r.type('list').should == "list"
@r.llen('list').should == 1
end
#
it "should be able to pop the tail of a list (RPOP)" do
@r.rpush "list", 'hello'
@r.rpush"list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.rpop('list').should == 'goodbye'
end
#
it "should be able to pop the head of a list (LPOP)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lpop('list').should == 'hello'
end
#
it "should be able to get the length of a list (LLEN)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
end
#
it "should be able to get a range of values from a list (LRANGE)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.rpush "list", '1'
@r.rpush "list", '2'
@r.rpush "list", '3'
@r.type('list').should == "list"
@r.llen('list').should == 5
@r.lrange('list', 2, -1).should == ['1', '2', '3']
end
#
it "should be able to trim a list (LTRIM)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.rpush "list", '1'
@r.rpush "list", '2'
@r.rpush "list", '3'
@r.type('list').should == "list"
@r.llen('list').should == 5
@r.ltrim 'list', 0, 1
@r.llen('list').should == 2
@r.lrange('list', 0, -1).should == ['hello', 'goodbye']
end
#
it "should be able to get a value by indexing into a list (LINDEX)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lindex('list', 1).should == 'goodbye'
end
#
it "should be able to set a value by indexing into a list (LSET)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'hello'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lset('list', 1, 'goodbye').should == 'OK'
@r.lindex('list', 1).should == 'goodbye'
end
#
it "should be able to remove values from a list (LREM)" do
@r.rpush "list", 'hello'
@r.rpush "list", 'goodbye'
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lrem('list', 1, 'hello').should == 1
@r.lrange('list', 0, -1).should == ['goodbye']
end
#
it "should be able add members to a set (SADD)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.type('set').should == "set"
@r.scard('set').should == 2
@r.smembers('set').sort.should == ['key1', 'key2'].sort
end
#
it "should be able delete members to a set (SREM)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.type('set').should == "set"
@r.scard('set').should == 2
@r.smembers('set').sort.should == ['key1', 'key2'].sort
@r.srem('set', 'key1')
@r.scard('set').should == 1
@r.smembers('set').should == ['key2']
end
#
it "should be able count the members of a set (SCARD)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.type('set').should == "set"
@r.scard('set').should == 2
end
#
it "should be able test for set membership (SISMEMBER)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.type('set').should == "set"
@r.scard('set').should == 2
@r.sismember('set', 'key1').should be_true
@r.sismember('set', 'key2').should be_true
@r.sismember('set', 'notthere').should be_false
end
#
it "should be able to do set intersection (SINTER)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.sadd "set2", 'key2'
@r.sinter('set', 'set2').should == ['key2']
end
#
it "should be able to do set intersection and store the results in a key (SINTERSTORE)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.sadd "set2", 'key2'
@r.sinterstore('newone', 'set', 'set2').should == 1
@r.smembers('newone').should == ['key2']
end
#
it "should be able to do set union (SUNION)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.sadd "set2", 'key2'
@r.sadd "set2", 'key3'
@r.sunion('set', 'set2').sort.should == ['key1','key2','key3'].sort
end
#
it "should be able to do set union and store the results in a key (SUNIONSTORE)" do
@r.sadd "set", 'key1'
@r.sadd "set", 'key2'
@r.sadd "set2", 'key2'
@r.sadd "set2", 'key3'
@r.sunionstore('newone', 'set', 'set2').should == 3
@r.smembers('newone').sort.should == ['key1','key2','key3'].sort
end
#
it "should be able to do set difference (SDIFF)" do
@r.sadd "set", 'a'
@r.sadd "set", 'b'
@r.sadd "set2", 'b'
@r.sadd "set2", 'c'
@r.sdiff('set', 'set2').should == ['a']
end
#
it "should be able to do set difference and store the results in a key (SDIFFSTORE)" do
@r.sadd "set", 'a'
@r.sadd "set", 'b'
@r.sadd "set2", 'b'
@r.sadd "set2", 'c'
@r.sdiffstore('newone', 'set', 'set2')
@r.smembers('newone').should == ['a']
end
#
it "should be able move elements from one set to another (SMOVE)" do
@r.sadd 'set1', 'a'
@r.sadd 'set1', 'b'
@r.sadd 'set2', 'x'
@r.smove('set1', 'set2', 'a').should be_true
@r.sismember('set2', 'a').should be_true
@r.delete('set1')
end
#
it "should be able to do crazy SORT queries" do
# The 'Dogs' is capitialized on purpose
@r['dog_1'] = 'louie'
@r.rpush 'Dogs', 1
@r['dog_2'] = 'lucy'
@r.rpush 'Dogs', 2
@r['dog_3'] = 'max'
@r.rpush 'Dogs', 3
@r['dog_4'] = 'taj'
@r.rpush 'Dogs', 4
@r.sort('Dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie']
@r.sort('Dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj']
end
it "should be able to handle array of :get using SORT" do
@r['dog:1:name'] = 'louie'
@r['dog:1:breed'] = 'mutt'
@r.rpush 'dogs', 1
@r['dog:2:name'] = 'lucy'
@r['dog:2:breed'] = 'poodle'
@r.rpush 'dogs', 2
@r['dog:3:name'] = 'max'
@r['dog:3:breed'] = 'hound'
@r.rpush 'dogs', 3
@r['dog:4:name'] = 'taj'
@r['dog:4:breed'] = 'terrier'
@r.rpush 'dogs', 4
@r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1]).should == ['louie', 'mutt']
@r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1], :order => 'desc alpha').should == ['taj', 'terrier']
end
#
it "should provide info (INFO)" do
[:last_save_time, :redis_version, :total_connections_received, :connected_clients, :total_commands_processed, :connected_slaves, :uptime_in_seconds, :used_memory, :uptime_in_days, :changes_since_last_save].each do |x|
@r.info.keys.should include(x)
end
end
#
it "should be able to flush the database (FLUSHDB)" do
@r['key1'] = 'keyone'
@r['key2'] = 'keytwo'
@r.keys('*').sort.should == ['foo', 'key1', 'key2'].sort #foo from before
@r.flushdb
@r.keys('*').should == []
end
#
it "should raise exception when manually try to change the database" do
lambda { @r.select(0) }.should raise_error
end
#
it "should be able to provide the last save time (LASTSAVE)" do
savetime = @r.lastsave
Time.at(savetime).class.should == Time
Time.at(savetime).should <= Time.now
end
it "should be able to MGET keys" do
@r['foo'] = 1000
@r['bar'] = 2000
@r.mget('foo', 'bar').should == ['1000', '2000']
@r.mget('foo', 'bar', 'baz').should == ['1000', '2000', nil]
end
it "should be able to mapped MGET keys" do
@r['foo'] = 1000
@r['bar'] = 2000
@r.mapped_mget('foo', 'bar').should == { 'foo' => '1000', 'bar' => '2000'}
@r.mapped_mget('foo', 'baz', 'bar').should == { 'foo' => '1000', 'bar' => '2000'}
end
it "should bgsave" do
@r.bgsave.should == 'OK'
end
it "should be able to ECHO" do
@r.echo("message in a bottle\n").should == "message in a bottle\n"
end
it "should raise error when invoke MONITOR" do
lambda { @r.monitor }.should raise_error
end
it "should raise error when invoke SYNC" do
lambda { @r.sync }.should raise_error
end
it "should handle multiple servers" do
require 'dist_redis'
@r = DistRedis.new(:hosts=> ['localhost:6379', '127.0.0.1:6379'], :db => 15)
100.times do |idx|
@r[idx] = "foo#{idx}"
end
100.times do |idx|
@r[idx].should == "foo#{idx}"
end
end
it "should be able to pipeline writes" do
@r.pipelined do |pipeline|
pipeline.lpush 'list', "hello"
pipeline.lpush 'list', 42
end
@r.type('list').should == "list"
@r.llen('list').should == 2
@r.lpop('list').should == '42'
end
it "should AUTH when connecting with a password" do
r = Redis.new(:password => 'secret')
r.stub!(:connect_to)
r.should_receive(:call_command).with(['auth', 'secret'])
r.connect_to_server
end
end

View File

@ -1,4 +0,0 @@
require 'rubygems'
$TESTING=true
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
require 'redis'

View File

@ -1,16 +0,0 @@
require 'benchmark'
require "#{File.dirname(__FILE__)}/lib/redis"
r = Redis.new
n = (ARGV.shift || 20000).to_i
elapsed = Benchmark.realtime do
# n sets, n gets
n.times do |i|
key = "foo#{i}"
r[key] = key * 10
r[key]
end
end
puts '%.2f Kops' % (2 * n / 1000 / elapsed)

View File

@ -1,125 +0,0 @@
# Inspired by rabbitmq.rake the Redbox project at http://github.com/rick/redbox/tree/master
require 'fileutils'
require 'open-uri'
class RedisRunner
def self.redisdir
"/tmp/redis/"
end
def self.redisconfdir
'/etc/redis.conf'
end
def self.dtach_socket
'/tmp/redis.dtach'
end
# Just check for existance of dtach socket
def self.running?
File.exists? dtach_socket
end
def self.start
puts 'Detach with Ctrl+\ Re-attach with rake redis:attach'
sleep 3
exec "dtach -A #{dtach_socket} redis-server #{redisconfdir}"
end
def self.attach
exec "dtach -a #{dtach_socket}"
end
def self.stop
sh 'echo "SHUTDOWN" | nc localhost 6379'
end
end
namespace :redis do
desc 'About redis'
task :about do
puts "\nSee http://code.google.com/p/redis/ for information about redis.\n\n"
end
desc 'Start redis'
task :start do
RedisRunner.start
end
desc 'Stop redis'
task :stop do
RedisRunner.stop
end
desc 'Restart redis'
task :restart do
RedisRunner.stop
RedisRunner.start
end
desc 'Attach to redis dtach socket'
task :attach do
RedisRunner.attach
end
desc 'Install the lastest verison of Redis from Github (requires git, duh)'
task :install => [:about, :download, :make] do
%w(redis-benchmark redis-cli redis-server).each do |bin|
sh "sudo cp /tmp/redis/#{bin} /usr/bin/"
end
puts "Installed redis-benchmark, redis-cli and redis-server to /usr/bin/"
unless File.exists?('/etc/redis.conf')
sh 'sudo cp /tmp/redis/redis.conf /etc/'
puts "Installed redis.conf to /etc/ \n You should look at this file!"
end
end
task :make do
sh "cd #{RedisRunner.redisdir} && make clean"
sh "cd #{RedisRunner.redisdir} && make"
end
desc "Download package"
task :download do
sh 'rm -rf /tmp/redis/' if File.exists?("#{RedisRunner.redisdir}/.svn")
sh 'git clone git://github.com/antirez/redis.git /tmp/redis' unless File.exists?(RedisRunner.redisdir)
sh "cd #{RedisRunner.redisdir} && git pull" if File.exists?("#{RedisRunner.redisdir}/.git")
end
end
namespace :dtach do
desc 'About dtach'
task :about do
puts "\nSee http://dtach.sourceforge.net/ for information about dtach.\n\n"
end
desc 'Install dtach 0.8 from source'
task :install => [:about] do
Dir.chdir('/tmp/')
unless File.exists?('/tmp/dtach-0.8.tar.gz')
require 'net/http'
url = 'http://downloads.sourceforge.net/project/dtach/dtach/0.8/dtach-0.8.tar.gz'
open('/tmp/dtach-0.8.tar.gz', 'wb') do |file| file.write(open(url).read) end
end
unless File.directory?('/tmp/dtach-0.8')
system('tar xzf dtach-0.8.tar.gz')
end
Dir.chdir('/tmp/dtach-0.8/')
sh 'cd /tmp/dtach-0.8/ && ./configure && make'
sh 'sudo cp /tmp/dtach-0.8/dtach /usr/bin/'
puts 'Dtach successfully installed to /usr/bin.'
end
end

View File

@ -1,6 +0,0 @@
.DS_Store
lib_managed
project/boot
target
target/
target/**/*

View File

@ -1,46 +0,0 @@
# Redis Scala client
## Key features of the library
- Native Scala types Set and List responses.
- Consisten Hashing on the client.
- Support for Clustering of Redis nodes.
## Information about redis
Redis is a key-value database. It is similar to memcached but the dataset is not volatile, and values can be strings, exactly like in memcached, but also lists and sets with atomic operations to push/pop elements.
http://code.google.com/p/redis/
### Key features of Redis
- Fast in-memory store with asynchronous save to disk.
- Key value get, set, delete, etc.
- Atomic operations on sets and lists, union, intersection, trim, etc.
## Requirements
- sbt (get it at http://code.google.com/p/simple-build-tool/)
## Usage
Start your redis instance (usually redis-server will do it)
$ cd scala-redis
$ sbt
> update
> test (optional to run the tests)
> console
And you are ready to start issuing commands to the server(s)
let's connect and get a key:
scala> import com.redis._
scala> val r = new Redis("localhost", 6379)
scala> val r.set("key", "some value")
scala> val r.get("key")
Alejandro Crosa <<alejandrocrosa@gmail.com>>

View File

@ -1,8 +0,0 @@
#Project properties
#Wed Aug 19 07:54:05 ART 2009
project.organization=com.redis
project.name=RedisClient
sbt.version=0.5.1
project.version=1.0.1
scala.version=2.7.5
project.initialize=false

View File

@ -1,12 +0,0 @@
import sbt._
class RedisClientProject(info: ProjectInfo) extends DefaultProject(info) with AutoCompilerPlugins
{
override def useDefaultConfigurations = true
val scalatest = "org.scala-tools.testing" % "scalatest" % "0.9.5" % "test->default"
val specs = "org.scala-tools.testing" % "specs" % "1.5.0"
val mockito = "org.mockito" % "mockito-all" % "1.7"
val junit = "junit" % "junit" % "4.5"
val sxr = compilerPlugin("org.scala-tools.sxr" %% "sxr" % "0.2.1")
}

View File

@ -1,8 +0,0 @@
package com.redis
/**
* Redis client Connection
*
*/
case class Connection(val host: String, val port: Int) extends SocketOperations

View File

@ -1,66 +0,0 @@
package com.redis
/**
* Hash Ring
*
*/
import java.util.zip.CRC32
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.Map
trait HashRing {
val replicas: Int
var sortedKeys: List[Long] = List()
var cluster = new ArrayBuffer[Redis]
val ring = Map[Long, Redis]()
// Adds the node to the hashRing
// including a number of replicas.
def addNode(node: Redis) = {
cluster += node
(1 to replicas).foreach{ replica =>
val key = calculateChecksum(node+":"+replica)
ring += (key -> node)
sortedKeys = sortedKeys ::: List(key)
}
sortedKeys = sortedKeys.sort(_ < _)
}
// get the node in the hash ring for this key
def getNode(key: String) = getNodePos(key)._1
def getNodePos(key: String): (Redis, Int) = {
val crc = calculateChecksum(key)
val idx = binarySearch(crc)
(ring(sortedKeys(idx)), idx)
}
// TODO this should perform a Bynary search
def binarySearch(value: Long): Int = {
var upper = (sortedKeys.length -1)
var lower = 0
var idx = 0
var comp: Long = 0
while(lower <= upper){
idx = (lower + upper) / 2
comp = sortedKeys(idx)
if(comp == value) { return idx }
if(comp < value) { upper = idx -1 }
if(comp > value) { lower = idx +1 }
}
return upper
}
// Computes the CRC-32 of the given String
def calculateChecksum(value: String): Long = {
val checksum = new CRC32
checksum.update(value.getBytes)
checksum.getValue
}
}

View File

@ -1,47 +0,0 @@
package com.redis.operations
/**
* Redis key space operations
*
*/
trait KeySpaceOperations{
val connection: Connection
var db: Int
// KEYS
// returns all the keys matching the glob-style pattern.
def keys(pattern: String): Array[String] = {
connection.write("KEYS "+pattern+"\r\n")
connection.readResponse.toString.split(" ")
}
// RANDKEY
// return a randomly selected key from the currently selected DB.
def randomKey: String = {
connection.write("RANDOMKEY\r\n")
connection.readResponse.toString.split('+')(1)
}
// RENAME (oldkey, newkey)
// atomically renames the key oldkey to newkey.
def rename(oldkey: String, newkey: String): Boolean = {
connection.write("RENAME "+oldkey+" "+newkey+"\r\n")
connection.readBoolean
}
// RENAMENX (oldkey, newkey)
// rename oldkey into newkey but fails if the destination key newkey already exists.
def renamenx(oldkey: String, newkey: String): Boolean = {
connection.write("RENAMENX "+oldkey+" "+newkey+"\r\n")
connection.readBoolean
}
// DBSIZE
// return the size of the db.
def dbSize: Int = {
connection.write("DBSIZE\r\n")
connection.readInt
}
}

View File

@ -1,94 +0,0 @@
package com.redis.operations
/**
* Redis list operations
*
*/
trait ListOperations{
def getConnection(key: String): Connection
// add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key.
// If the key does not exist an empty list is created just before the append operation. If the key exists but is not a List an error is returned.
// LPUSH
def pushHead(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("LPUSH "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// RPUSH
def pushTail(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("RPUSH "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// LPOP
// atomically return and remove the first (LPOP) or last (RPOP) element of the list
def popHead(key: String): String = {
val connection = getConnection(key)
connection.write("LPOP "+key+"\r\n")
connection.readString
}
// RPOP
// atomically return and remove the first (LPOP) or last (RPOP) element of the list
def popTail(key: String): String = {
val connection = getConnection(key)
connection.write("RPOP "+key+"\r\n")
connection.readString
}
// LINDEX
// return the especified element of the list stored at the specified key. 0 is the first element, 1 the second and so on.
// Negative indexes are supported, for example -1 is the last element, -2 the penultimate and so on.
def listIndex(key: String, index: Int): String = {
val connection = getConnection(key)
connection.write("LINDEX "+key+" "+index+"\r\n")
connection.readString
}
// LSET
// set the list element at index with the new value. Out of range indexes will generate an error
def listSet(key: String, index: Int, value: String): Boolean = {
val connection = getConnection(key)
connection.write("LSET "+key+" "+index+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// LLEN
// return the length of the list stored at the specified key.
// If the key does not exist zero is returned (the same behaviour as for empty lists). If the value stored at key is not a list an error is returned.
def listLength(key: String): Int = {
val connection = getConnection(key)
connection.write("LLEN "+key+"\r\n")
connection.readInt
}
// LRANGE
// return the specified elements of the list stored at the specified key.
// Start and end are zero-based indexes. 0 is the first element of the list (the list head), 1 the next element and so on.
def listRange(key: String, start: Int, end: Int): List[String] = {
val connection = getConnection(key)
connection.write("LRANGE "+key+" "+start+" "+end+"\r\n")
connection.readList
}
// LTRIM
// Trim an existing list so that it will contain only the specified range of elements specified.
def listTrim(key: String, start: Int, end: Int): Boolean = {
val connection = getConnection(key)
connection.write("LTRIM "+key+" "+start+" "+end+"\r\n")
connection.readBoolean
}
// LREM
// Remove the first count occurrences of the value element from the list.
def listRem(key: String, count: Int, value: String): Boolean = {
val connection = getConnection(key)
connection.write("LREM "+key+" "+count+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
}

View File

@ -1,121 +0,0 @@
package com.redis.operations
/**
* Redis node operations
*
*/
trait NodeOperations {
val connection: Connection
var db: Int
// SAVE
// save the DB on disk now.
def save: Boolean = {
connection.write("SAVE\r\n")
connection.readBoolean
}
// BGSAVE
// save the DB in the background.
def bgSave: Boolean = {
connection.write("BGSAVE\r\n")
connection.readBoolean
}
// LASTSAVE
// return the UNIX TIME of the last DB SAVE executed with success.
def lastSave: Int = {
connection.write("LASTSAVE\r\n")
connection.readInt
}
// SHUTDOWN
// Stop all the clients, save the DB, then quit the server.
def shutdown: Boolean = {
connection.write("SHUTDOWN\r\n")
connection.readBoolean
}
// MGET (key, key, key, ...)
// get the values of all the specified keys.
def mget(keys: String*) = {
connection.write("MGET "+keys.mkString(" ")+"\r\n")
connection.readList
}
// INFO
// the info command returns different information and statistics about the server.
def info = {
connection.write("INFO\r\n")
connection.readResponse
}
// MONITOR
// is a debugging command that outputs the whole sequence of commands received by the Redis server.
def monitor: Boolean = {
connection.write("MONITOR\r\n")
connection.readBoolean
}
// SLAVEOF
// The SLAVEOF command can change the replication settings of a slave on the fly.
def slaveOf(options: Any): Boolean = options match {
case (host: String, port: Int) => {
connection.write("SLAVEOF "+host+" "+port.toString+"\r\n")
connection.readBoolean
}
case _ => setAsMaster
}
def setAsMaster: Boolean = {
connection.write("SLAVEOF NO ONE\r\n")
connection.readBoolean
}
// SELECT (index)
// selects the DB to connect, defaults to 0 (zero).
def selectDb(index: Int): Boolean = {
connection.write("SELECT "+index+"\r\n")
connection.readBoolean match {
case true => { db = index; true }
case _ => false
}
}
// FLUSHDB the DB
// removes all the DB data.
def flushDb: Boolean = {
connection.write("FLUSHDB\r\n")
connection.readBoolean
}
// FLUSHALL the DB's
// removes data from all the DB's.
def flushAll: Boolean = {
connection.write("FLUSHALL\r\n")
connection.readBoolean
}
// MOVE
// Move the specified key from the currently selected DB to the specified destination DB.
def move(key: String, db: Int) = {
connection.write("MOVE "+key+" "+db.toString+"\r\n")
connection.readBoolean
}
// QUIT
// exits the server.
def quit: Boolean = {
connection.write("QUIT\r\n")
connection.disconnect
}
// AUTH
// auths with the server.
def auth(secret: String): Boolean = {
connection.write("AUTH "+secret+"\r\n")
connection.readBoolean
}
}

View File

@ -1,124 +0,0 @@
package com.redis.operations
/**
* Redis operations
*
*/
trait Operations{
def getConnection(key: String): Connection
// SET (key, value)
// SET (key, value, expiry)
// sets the key with the specified value, and with an optional expiry.
def set(key: String, value: String) = setKey(key, value)
def set(key: String, value: String, expiry: Int) = { setKey(key, value) && expire(key, expiry) }
// SET KEY (key, value)
// sets the key with the specified value.
def setKey(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SET "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// EXPIRE (key, expiry)
// sets the expire time (in sec.) for the specified key.
def expire(key: String, expiry: Int): Boolean = {
val connection = getConnection(key)
connection.write("EXPIRE "+key+" "+expiry+"\r\n")
connection.readBoolean
}
// GET (key)
// gets the value for the specified key.
def get(key: String): String = {
val connection = getConnection(key)
val a = connection.write("GET "+key+"\r\n")
connection.readResponse match {
case r: String => r.toString
case _ => null
}
}
// GETSET (key, value)
// is an atomic set this value and return the old value command.
def getSet(key: String, value: String): String = {
val connection = getConnection(key)
val a = connection.write("GETSET "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readResponse match {
case r: String => r.toString
case _ => null
}
}
// SETNX (key, value)
// sets the value for the specified key, only if the key is not there.
def setUnlessExists(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SETNX "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// DELETE (key)
// deletes the specified key.
def delete(key: String): Boolean = {
val connection = getConnection(key)
connection.write("DEL "+key+"\r\n")
connection.readBoolean
}
// INCR (key)
// INCR (key, increment)
// increments the specified key, optional the increment value.
def incr(x: Any): Int = x match {
case (key: String, increment: Int) => incrBy(key, increment)
case (key: String) => incrOne(key)
case _ => 0
}
def incrBy(key: String, increment: Int): Int = {
val connection = getConnection(key)
connection.write("INCRBY "+key+" "+increment+"\r\n")
connection.readInt
}
def incrOne(key: String): Int = {
val connection = getConnection(key)
connection.write("INCR "+key+"\r\n")
connection.readInt
}
// DECR (key)
// DECRBY (key, decrement)
// decrements the specified key, optional the decrement value.
def decr(key: String, decrement: Int) = decrBy(key, decrement)
def decr(key: String) = decrOne(key)
def decrBy(key: String, decrement: Int): Int = {
val connection = getConnection(key)
connection.write("DECRBY "+key+" "+decrement+"\r\n")
connection.readInt
}
def decrOne(key: String): Int = {
val connection = getConnection(key)
connection.write("DECR "+key+"\r\n")
connection.readInt
}
// EXISTS (key)
// test if the specified key exists.
def exists(key: String): Boolean = {
val connection = getConnection(key)
connection.write("EXISTS "+key+"\r\n")
connection.readBoolean
}
// TYPE (key)
// return the type of the value stored at key in form of a string.
def getType(key: String): Any = {
val connection = getConnection(key)
connection.write("TYPE "+key+"\r\n")
connection.readResponse
}
}

View File

@ -1,115 +0,0 @@
package com.redis.operations
/**
* Redis set operations
*
*/
trait SetOperations{
def getConnection(key: String): Connection
// SADD
// Add the specified member to the set value stored at key.
def setAdd(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SADD "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// SREM
// Remove the specified member from the set value stored at key.
def setDelete(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SREM "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// SCARD
// Return the number of elements (the cardinality) of the Set at key.
def setCount(key: String): Int = {
val connection = getConnection(key)
connection.write("SCARD "+key+"\r\n")
connection.readInt
}
// SMEMBERS
// Return all the members of the Set value at key.
def setMembers(key: String): Set[String] = {
val connection = getConnection(key)
connection.write("SMEMBERS "+key+"\r\n")
connection.readSet
}
// SPOP
// Remove and return (pop) a random element from the Set value at key.
def setPop(key: String): String = {
val connection = getConnection(key)
connection.write("SPOP "+key+"\r\n")
connection.readString
}
// SMOVE
// Move the specified member from one Set to another atomically.
def setMove(sourceKey: String, destKey: String, value: String): Boolean = {
val connection = getConnection(sourceKey)
connection.write("SMOVE "+sourceKey+" "+destKey+" "+value+"\r\n")
connection.readBoolean
}
// SISMEMBER
// Test if the specified value is a member of the Set at key.
def setMemberExists(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SISMEMBER "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// SINTER
// Return the intersection between the Sets stored at key1, key2, ..., keyN.
def setIntersect(keys: String*): Set[String] = {
val connection = getConnection(keys(0))
connection.write("SINTER "+keys.mkString(" ")+"\r\n")
connection.readSet
}
// SINTERSTORE
// Compute the intersection between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey.
def setInterStore(key: String, keys: String*): Boolean = {
val connection = getConnection(key)
connection.write("SINTERSTORE "+key+" "+keys.mkString(" ")+"\r\n")
connection.readBoolean
}
// SDIFF
// Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN.
def setDiff(keys: String*): Set[String] = {
val connection = getConnection(keys(0))
connection.write("SDIFF "+keys.mkString(" ")+"\r\n")
connection.readSet
}
// SDIFFSTORE
// Compute the difference between the Set key1 and all the Sets key2, ..., keyN, and store the resulting Set at dstkey.
def setDiffStore(key: String, keys: String*): Boolean = {
val connection = getConnection(key)
connection.write("SDIFFSTORE "+key+" "+keys.mkString(" ")+"\r\n")
connection.readBoolean
}
// SUNION
// Return the union between the Sets stored at key1, key2, ..., keyN.
def setUnion(keys: String*): Set[String] = {
val connection = getConnection(keys(0))
connection.write("SUNION "+keys.mkString(" ")+"\r\n")
connection.readSet
}
// SUNIONSTORE
// Compute the union between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey.
def setUnionStore(key: String, keys: String*): Boolean = {
val connection = getConnection(key)
connection.write("SUNIONSTORE "+key+" "+keys.mkString(" ")+"\r\n")
connection.readBoolean
}
}

View File

@ -1,28 +0,0 @@
package com.redis.operations
/**
* Redis sort operations
*
*/
trait SortOperations{
def getConnection(key: String): Connection
// SORT
// Sort a Set or a List accordingly to the specified parameters.
def sort(args: Any): List[String] = args match {
case (key: String, command: String) => doSort(key, command)
case (key: String) => doSort(key, "")
}
def doSort(key: String, command: String): List[String] = {
val connection = getConnection(key)
if(command != "") {
connection.write("SORT "+key+" "+command+"\r\n")
} else {
connection.write("SORT "+key+"\r\n")
}
connection.readList
}
}

View File

@ -1,33 +0,0 @@
package com.redis
import com.redis.operations._
/**
* Redis client
*
*/
class Redis(val host: String, val port: Int) extends Operations with ListOperations with SetOperations with NodeOperations with KeySpaceOperations with SortOperations {
// auxiliary constructor
def this() = this("localhost", 6379)
// Points to the connection to a server instance
val connection = Connection(host, port)
var db: Int = 0
// Connect and Disconnect to the Redis server
def connect = connection.connect
def disconnect = connection.disconnect
def connected: Boolean = connection.connected
// Establish the connection to the server instance on initialize
connect
// Get Redis Client connection.
def getConnection(key: String) = getConnection
def getConnection = connection
// Outputs a formatted representation of the Redis server.
override def toString = connection.host+":"+connection.port+" <connected:"+connection.connected+">"
}

View File

@ -1,42 +0,0 @@
package com.redis
import com.redis.operations._
/**
* Redis cluster
*
*/
import scala.collection.mutable.ArrayBuffer
class RedisCluster(val hosts: String*) extends Operations with ListOperations with SetOperations with HashRing with SortOperations {
// Get Redis Client connection inside the HashRing.
def getConnection(key: String) = {
getNode(key).connection
}
// Default value used on MemCache client.
private val NUMBER_OF_REPLICAS = 160
val replicas = NUMBER_OF_REPLICAS
// Outputs a formatted representation of the Redis server.
override def toString = cluster.mkString(", ")
// Connect the client and add it to the cluster.
def connectClient(host: String): Boolean = {
val h = host.split(":")(0)
val p = host.split(":")(1)
val client = new Redis(h.toString, p.toString.toInt)
addNode(client)
client.connected
}
// Connect all clients in the cluster.
def connect = cluster.map(c => c.connect)
// Initialize cluster.
def initialize_cluster = hosts.map(connectClient(_))
initialize_cluster
}

View File

@ -1,167 +0,0 @@
package com.redis
/**
* Socket operations
*
*/
import java.io._
import java.net.Socket
trait SocketOperations {
// Response codes from the Redis server
// they tell you what's coming next from the server.
val ERR: String = "-"
val OK: String = "+OK"
val SINGLE: String = "+"
val BULK: String = "$"
val MULTI: String = "*"
val INT:String = ":"
val host: String
val port: Int
// File descriptors.
var socket: Socket = null
var out: OutputStream = null
var in: BufferedReader = null
def getOutputStream: OutputStream = out
def getInputStream: BufferedReader = in
def getSocket: Socket = socket
def connected = { getSocket != null }
def reconnect = { disconnect && connect; }
// Connects the socket, and sets the input and output streams.
def connect: Boolean = {
try {
socket = new Socket(host, port)
out = getSocket.getOutputStream
in = new BufferedReader(new InputStreamReader(getSocket.getInputStream));
true
} catch {
case _ => clear_fd; false;
}
}
// Disconnects the socket.
def disconnect: Boolean = {
try {
socket.close
out.close
in.close
clear_fd
true
} catch {
case _ => false
}
}
def clear_fd = {
socket = null
out = null
in = null
}
// Reads the server responses as Scala types.
def readString: String = readResponse.toString // Reads the server response as an Int
def readInt: Int = Integer.parseInt(readResponse.toString) // Reads the server response as an Int
def readList: List[String] = listReply(readResponse.toString) // Reads the server response as a List
def readSet: Set[String] = setReply(readResponse.toString) // Reads the server response as a String
def readBoolean: Boolean = readResponse match {
case 1 => true
case OK => true
case _ => false
}
// Read from Input Stream.
def readline: String = {
try {
getInputStream.readLine()
} catch {
case _ => ERR;
}
}
// Gets the type of response the server is going to send.
def readtype = {
val res = readline
if(res !=null){
(res(0).toString(), res)
}else{
("-", "")
}
}
// Reads the response from the server based on the response code.
def readResponse = {
val responseType = readtype
try{
responseType._1 match {
case ERR => reconnect; // RECONNECT
case SINGLE => lineReply(responseType._2)
case BULK => bulkReply(responseType._2)
case MULTI => responseType._2
case INT => integerReply(responseType._2)
case _ => reconnect; // RECONNECT
}
}catch{
case e: Exception => false
}
}
def integerReply(response: String): Int = Integer.parseInt(response.split(":")(1).toString)
def lineReply(response: String): String = response
def listReply(response: String): List[String] = {
val total = Integer.parseInt(response.split('*')(1))
var list: List[String] = List()
for(i <- 1 to total){
list = (list ::: List(bulkReply(readtype._2)))
}
list
}
def bulkReply(response: String) = {
if(response(1).toString() != ERR){
var length: Int = Integer.parseInt(response.split('$')(1).split("\r\n")(0))
var line, res: String = ""
while(length >= 0){
line = readline
length -= (line.length+2)
res += line
if(length > 0) res += "\r\n"
}
res
}else{ null }
}
def setReply(response: String): Set[String] = {
val total = Integer.parseInt(response.split('*')(1))
var set: Set[String] = Set()
for(i <- 1 to total){
set += bulkReply(readtype._2)
}
set
}
// Wraper for the socket write operation.
def write_to_socket(data: String)(op: OutputStream => Unit) = op(getOutputStream)
// Writes data to a socket using the specified block.
def write(data: String) = {
if(!connected) connect;
write_to_socket(data){
getSocket =>
try {
getSocket.write(data.getBytes)
} catch {
case _ => reconnect;
}
}
}
}

View File

@ -1,27 +0,0 @@
import org.specs._
import com.redis._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
object RedisClientSpec extends Specification with Mockito {
"Redis Client" should {
var client: Redis = null
"print formatted client status" in {
client = new Redis("localhost", 121212)
client.toString must be matching("localhost:121212 <connected:false>")
}
"get the same connection when passing key para or not since it's a single node" in {
client.getConnection("key") mustEqual client.getConnection
}
"use db zero as default" in {
client.db mustEqual 0
}
}
}

View File

@ -1,51 +0,0 @@
import org.specs._
import com.redis._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
object RedisClusterSpec extends Specification with Mockito {
"Redis Cluster" should {
var cluster: RedisCluster = null
var mockedRedis: Redis = null
doBefore {
cluster = new RedisCluster("localhost:11221", "localhost:99991")
mockedRedis = mock[Redis]
}
"print formatted client status" in {
cluster.toString must be matching("localhost:11221 <connected:false>, localhost:99991 <connected:false>")
}
"get the connection for the specified key" in {
cluster.getConnection("key") mustEqual Connection("localhost", 99991)
cluster.getConnection("anotherkey") mustEqual Connection("localhost", 11221)
}
"use the default number of replicas" in {
cluster.replicas mustEqual 160
}
"initialize cluster" in {
val initializedCluster = cluster.initialize_cluster
initializedCluster.size mustEqual 2
initializedCluster(0) mustEqual false
initializedCluster(1) mustEqual false
}
"connect all the redis instances" in {
cluster.cluster(1) = mockedRedis
cluster.cluster(1).connect returns true
val connectResult = cluster.connect
connectResult.size mustEqual 2
connectResult(0) mustEqual false
connectResult(1) mustEqual true
cluster.cluster(1).connect was called
}
}
}

View File

@ -1,97 +0,0 @@
import org.specs._
import com.redis._
import java.io._
import java.net.Socket
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
class SocketOperationTest(val host:String, val port: Int) extends SocketOperations
object SocketOperationsSpec extends Specification with Mockito {
"Socket Operations" should {
var socketOperation: SocketOperationTest = null
var socket: Socket = null
var in: BufferedReader = null
doBefore {
socketOperation = new SocketOperationTest("localhost", 6379666)
socket = mock[Socket]
in = mock[BufferedReader]
socketOperation.socket = socket
socketOperation.in = in
}
def readOkFromInput = { when(in.readLine()).thenReturn(socketOperation.OK) }
def readSingleFromInput = { when(in.readLine()).thenReturn(socketOperation.SINGLE) }
def readBulkFromInput = { in.readLine() returns("$6\r\nfoobar\r\n") thenReturns("$6\r\nfoobar\r\n") }
def readIntFromInput = { when(in.readLine()).thenReturn(socketOperation.INT+"666") }
"tell if it's connected" in {
socketOperation.connected mustEqual true
socketOperation.socket = null
socketOperation.connected mustEqual false
}
"return false when can't connect" in {
socketOperation.connect mustEqual false
}
"return current data input stream" in {
socketOperation.getInputStream mustEqual in
}
"read a line from socket" in {
readOkFromInput
socketOperation.in mustEqual in
socketOperation.readline mustEqual socketOperation.OK
}
"read type response" in {
readOkFromInput
socketOperation.readtype mustEqual ("+", socketOperation.OK)
}
"when reading responses" in {
"read OK" in {
readOkFromInput
socketOperation.readResponse mustEqual socketOperation.OK
}
"read single line" in {
readSingleFromInput
socketOperation.readResponse mustEqual socketOperation.SINGLE
}
"reconnect on error" in {
socketOperation.readResponse mustEqual false
socket.close was called
socketOperation.connected mustEqual true
}
"read in bulk" in {
// readBulkFromInput
// this shouldn't be the response, it doesn't seem to work return and then returns.
// Here's what should happen: '$6\r\n' on first readLine and then 'foobar\r\n'
readBulkFromInput
socketOperation.readtype mustEqual ("$", "$6\r\nfoobar\r\n")
socketOperation.readResponse mustEqual "$6\r\nfoobar\r\n"
socketOperation.bulkReply("$6\r\nfoobar\r\n") was called
}
"read integer" in {
readIntFromInput
socketOperation.readInt mustEqual 666
}
"read a boolean return value" in {
readOkFromInput
socketOperation.readBoolean mustEqual true
}
}
}
}

View File

@ -1,13 +0,0 @@
import org.specs._
import com.redis._
import com.redis.operations._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
class RedisTestClient(val connection: Connection) extends Operations with ListOperations with SetOperations with NodeOperations with KeySpaceOperations with SortOperations {
var db: Int = 0
def getConnection(key: String): Connection = connection
}

View File

@ -1,50 +0,0 @@
import org.specs._
import com.redis._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
object KeySpaceOperationsSpec extends Specification with Mockito {
"Redis Client Key Operations" should {
var client: RedisTestClient = null
var connection: Connection = null
doBefore{
connection = mock[Connection]
client = new RedisTestClient(connection)
}
"return all keys matching" in {
connection.readResponse returns "akey anotherkey adiffkey"
client.keys("a*")
connection.write("KEYS a*\r\n") was called
}
"return a random key" in {
connection.readResponse returns "+somerandonkey"
client.randomKey mustEqual "somerandonkey"
connection.write("RANDOMKEY\r\n") was called
}
"remame a key" in {
connection.readBoolean returns true
client.rename("a", "b") must beTrue
connection.write("RENAME a b\r\n") was called
}
"rename a key only if destintation doesn't exist" in {
connection.readBoolean returns false
client.renamenx("a", "b") must beFalse
connection.write("RENAMENX a b\r\n") was called
}
"tell the size of the db, # of keys" in {
connection.readInt returns 4
client.dbSize mustEqual 4
connection.write("DBSIZE\r\n") was called
}
}
}

View File

@ -1,81 +0,0 @@
import org.specs._
import com.redis._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
object ListOperationsSpec extends Specification with Mockito {
"Redis Client List Operations" should {
var client: RedisTestClient = null
var connection: Connection = null
doBefore{
connection = mock[Connection]
client = new RedisTestClient(connection)
}
"push to head" in {
connection.readBoolean returns true
client.pushHead("k", "v") must beTrue
connection.write("LPUSH k 1\r\nv\r\n") was called
}
"push to tail" in {
connection.readBoolean returns true
client.pushTail("k", "v") must beTrue
connection.write("RPUSH k 1\r\nv\r\n") was called
}
"pop from head" in {
connection.readString returns "value"
client.popHead("key") mustEqual "value"
connection.write("LPOP key\r\n") was called
}
"pop from tail" in {
connection.readString returns "value"
client.popTail("key") mustEqual "value"
connection.write("RPOP key\r\n") was called
}
"return list index" in {
connection.readString returns "value"
client.listIndex("k", 2) mustEqual "value"
connection.write("LINDEX k 2\r\n") was called
}
"return set element at index" in {
connection.readBoolean returns true
client.listSet("k", 1, "value") mustEqual true
connection.write("LSET k 1 5\r\nvalue\r\n") was called
}
"return list size" in {
connection.readInt returns 3
client.listLength("k") mustEqual 3
connection.write("LLEN k\r\n") was called
}
"return list range" in {
val listResult: List[String] = List("one", "two", "three", "four", "five")
connection.readList returns listResult
client.listRange("k", 2, 4) mustEqual listResult
connection.write("LRANGE k 2 4\r\n") was called
}
"trim a list" in {
connection.readBoolean returns true
client.listTrim("k", 2, 4) mustEqual true
connection.write("LTRIM k 2 4\r\n") was called
}
"remove occurrences of a value in the list" in {
connection.readBoolean returns true
client.listRem("k", 2, "value") mustEqual true
connection.write("LREM k 2 5\r\nvalue\r\n") was called
}
}
}

View File

@ -1,113 +0,0 @@
import org.specs._
import com.redis._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
object NodeOperationsSpec extends Specification with Mockito {
"Redis Client Node Operations" should {
var client: RedisTestClient = null
var connection: Connection = null
doBefore{
connection = mock[Connection]
client = new RedisTestClient(connection)
}
"save the db to disk" in {
connection.readBoolean returns true
client.save must beTrue
connection.write("SAVE\r\n") was called
}
"return the last time saved data to the db" in {
connection.readInt returns 1250421891
client.lastSave mustEqual 1250421891
connection.write("LASTSAVE\r\n") was called
}
"return all specified keys" in {
connection.readList returns List[String]("hola", null, null)
client.mget("a", "b", "c") mustEqual List[String]("hola", null, null)
connection.write("MGET a b c\r\n") was called
}
"return server info" in {
val sampleInfo = "res0: Any = \nredis_version:0.091\nconnected_clients:2\nconnected_slaves:0\nused_memory:3036\nchanges_since_last_save:0\nlast_save_time:1250440893\ntotal_connections_received:2\ntotal_commands_processed:0\nuptime_in_seconds:7\nuptime_in_days:0\n"
connection.readResponse returns sampleInfo
client.info mustEqual sampleInfo
connection.write("INFO\r\n") was called
}
"start monitor debug on the server" in {
connection.readBoolean returns true
client.monitor mustEqual true
connection.write("MONITOR\r\n") was called
}
"set a server as slave of a remote master" in {
connection.readBoolean returns true
client.slaveOf("localhost", 9999) mustEqual true
connection.write("SLAVEOF localhost 9999\r\n") was called
}
"set a server as master if no params sent" in {
connection.readBoolean returns true
client.slaveOf() mustEqual true
connection.write("SLAVEOF NO ONE\r\n") was called
}
"set a server as master" in {
connection.readBoolean returns true
client.setAsMaster mustEqual true
connection.write("SLAVEOF NO ONE\r\n") was called
}
"select the current db" in {
connection.readBoolean returns true
client.selectDb(3) mustEqual true
client.db mustEqual 3
connection.write("SELECT 3\r\n") was called
}
"flush the db" in {
connection.readBoolean returns true
client.flushDb mustEqual true
connection.write("FLUSHDB\r\n") was called
}
"flush all the dbs" in {
connection.readBoolean returns true
client.flushAll mustEqual true
connection.write("FLUSHALL\r\n") was called
}
"shutdown the db" in {
connection.readBoolean returns true
client.shutdown mustEqual true
connection.write("SHUTDOWN\r\n") was called
}
"move keys from one db to another" in {
connection.readBoolean returns true
client.move("a", 2) mustEqual true
connection.write("MOVE a 2\r\n") was called
}
"quit" in {
connection.disconnect returns true
client.quit mustEqual true
connection.write("QUIT\r\n") was called
connection.disconnect was called
}
"auth with the server" in {
connection.readBoolean returns true
client.auth("secret") mustEqual true
connection.write("AUTH secret\r\n") was called
}
}
}

View File

@ -1,105 +0,0 @@
import org.specs._
import com.redis._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
object OperationsSpec extends Specification with Mockito {
"Redis Client Operations" should {
var client: RedisTestClient = null
var connection: Connection = null
doBefore{
connection = mock[Connection]
client = new RedisTestClient(connection)
}
"set a key" in {
connection.readBoolean returns true
client.set("a", "b") mustEqual true
connection.write("SET a 1\r\nb\r\n") was called
}
"set a key with setKey" in {
connection.readBoolean returns true
client.setKey("a", "b") mustEqual true
connection.write("SET a 1\r\nb\r\n") was called
}
"set a key with expiration" in {
connection.readBoolean returns true
client.set("a", "b", 4) mustEqual true
connection.write("SET a 1\r\nb\r\n") was called
connection.write("EXPIRE a 4\r\n") was called
}
"expire a key" in {
connection.readBoolean returns true
client.expire("a", 4) mustEqual true
connection.write("EXPIRE a 4\r\n") was called
}
"get a key" in {
connection.readResponse returns "b"
client.get("a") mustEqual "b"
connection.write("GET a\r\n") was called
}
"get and set a key" in {
connection.readResponse returns "old"
client.getSet("a", "new") mustEqual "old"
connection.write("GETSET a 3\r\nnew\r\n") was called
}
"delete a key" in {
connection.readBoolean returns true
client.delete("a") mustEqual true
connection.write("DEL a\r\n") was called
}
"tell if a key exists" in {
connection.readBoolean returns true
client.exists("a") mustEqual true
connection.write("EXISTS a\r\n") was called
}
"tell if a key exists" in {
connection.readBoolean returns true
client.exists("a") mustEqual true
connection.write("EXISTS a\r\n") was called
}
"increment a value" in {
connection.readInt returns 1
client.incr("a") mustEqual 1
connection.write("INCR a\r\n") was called
}
"increment a value by N" in {
connection.readInt returns 27
client.incr("a", 23) mustEqual 27
connection.write("INCRBY a 23\r\n") was called
}
"decrement a value" in {
connection.readInt returns 0
client.decr("a") mustEqual 0
connection.write("DECR a\r\n") was called
}
"decrement a value by N" in {
connection.readInt returns 25
client.decr("a", 2) mustEqual 25
connection.write("DECRBY a 2\r\n") was called
}
"return type of key" in {
connection.readResponse returns "String"
client.getType("a") mustEqual "String"
connection.write("TYPE a\r\n") was called
}
}
}

View File

@ -1,102 +0,0 @@
import org.specs._
import com.redis._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
object SetOperationsSpec extends Specification with Mockito {
"Redis Client Set Operations" should {
var client: RedisTestClient = null
var connection: Connection = null
doBefore{
connection = mock[Connection]
client = new RedisTestClient(connection)
}
"add a member to a set" in {
connection.readBoolean returns true
client.setAdd("set", "value") must beTrue
connection.write("SADD set 5\r\nvalue\r\n") was called
}
"remove an member from a set" in {
connection.readBoolean returns true
client.setDelete("set", "value") must beTrue
connection.write("SREM set 5\r\nvalue\r\n") was called
}
"return the number of elements in the set" in {
connection.readInt returns 5
client.setCount("set") mustEqual 5
connection.write("SCARD set\r\n") was called
}
"return all the members from a set" in {
val setResult = Set("one", "two", "three")
connection.readSet returns setResult
client.setMembers("set") mustEqual setResult
connection.write("SMEMBERS set\r\n") was called
}
"pop an element from the set" in {
connection.readString returns "one"
client.setPop("set") mustEqual "one"
connection.write("SPOP set\r\n") was called
}
"move an element from one set to another" in {
connection.readBoolean returns true
client.setMove("set", "toset", "value") mustEqual true
connection.write("SMOVE set toset value\r\n") was called
}
"tell if member exists on the set" in {
connection.readBoolean returns true
client.setMemberExists("set", "value") mustEqual true
connection.write("SISMEMBER set 5\r\nvalue\r\n") was called
}
"return the intersection between N sets" in {
val setResult = Set("one", "two", "three")
connection.readSet returns setResult
client.setIntersect("set", "otherset", "another") mustEqual setResult
connection.write("SINTER set otherset another\r\n") was called
}
"return the intersection between N sets and store it a new one" in {
connection.readBoolean returns true
client.setInterStore("set", "oneset", "twoset") mustEqual true
connection.write("SINTERSTORE set oneset twoset\r\n") was called
}
"return the difference between N sets" in {
val setResult = Set("one", "two", "three")
connection.readSet returns setResult
client.setDiff("set", "oneset", "twoset") mustEqual setResult
connection.write("SDIFF set oneset twoset\r\n") was called
}
"return the difference between N sets and store it in a new one" in {
connection.readBoolean returns true
client.setDiffStore("newset", "oneset", "twoset") mustEqual true
connection.write("SDIFFSTORE newset oneset twoset\r\n") was called
}
"return the union between N sets" in {
val setResult = Set("one", "two", "three")
connection.readSet returns setResult
client.setUnion("set", "oneset", "twoset") mustEqual setResult
connection.write("SUNION set oneset twoset\r\n") was called
}
"return the union between N sets and store it in a new one" in {
connection.readBoolean returns true
client.setUnionStore("set", "oneset", "twoset") mustEqual true
connection.write("SUNIONSTORE set oneset twoset\r\n") was called
}
}
}

Some files were not shown because too many files have changed in this diff Show More