Commit 1259672f authored by antirez's avatar antirez
Browse files

client libs removed from Redis git

parent 5762b7f0
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.
classes
\#*
.\#*
*.jar
build.properties
\ No newline at end of file
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.
\ No newline at end of file
# 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
(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*)
(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)))))
<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>
;;
;; 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"))))
;(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))
)
(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)))
(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)))))
(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"))))
# 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
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
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)
/* 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;
}
/* 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
#ifndef _REDIS_FMACRO_H
#define _REDIS_FMACRO_H
#define _BSD_SOURCE
#define _XOPEN_SOURCE
#endif
This diff is collapsed.
/* 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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment