From 111d9959c05ac675bb713da589eb7b08fe876d56 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 16 Jun 2009 16:45:04 +0200 Subject: [PATCH] client libraries updated --- .../clojure/benchmarks/clojure.clj | 18 +++-- client-libraries/clojure/benchmarks/ruby.clj | 5 +- client-libraries/clojure/build.xml | 5 +- client-libraries/clojure/src/redis.clj | 6 +- .../clojure/src/redis/internal.clj | 6 +- client-libraries/clojure/src/redis/tests.clj | 22 ++++-- .../clojure/src/redis/tests/internal.clj | 5 -- client-libraries/cpp/TODO | 6 ++ client-libraries/cpp/redisclient.cpp | 74 ++++++++----------- client-libraries/cpp/redisclient.h | 13 +++- client-libraries/cpp/test_client.cpp | 4 +- client-libraries/lua/redis.lua | 19 ++++- client-libraries/ruby/.gitignore | 1 + client-libraries/ruby/Rakefile | 10 ++- client-libraries/ruby/spec/redis_spec.rb | 45 +++++++++-- client-libraries/update-clojure-client.sh | 12 +++ 16 files changed, 165 insertions(+), 86 deletions(-) create mode 100755 client-libraries/update-clojure-client.sh diff --git a/client-libraries/clojure/benchmarks/clojure.clj b/client-libraries/clojure/benchmarks/clojure.clj index 7f88d8ea8..13739f04e 100644 --- a/client-libraries/clojure/benchmarks/clojure.clj +++ b/client-libraries/clojure/benchmarks/clojure.clj @@ -30,7 +30,6 @@ :requests) - (defmacro defbenchmark [name & body] (let [benchmark-name (symbol (str name "-benchmark"))] `(def ~(with-meta benchmark-name {:benchmark true}) @@ -64,7 +63,7 @@ (redis/get (str "key-" (rand-int 1000)))) (defbenchmark set - (redis/set (str "key-" (rand-int 1000)) "blahojga!")) + (redis/set (str "key-" (rand-int 1000)) "abc")) (defbenchmark exists-set-and-get (let [key (str "key-" (rand-int 100))] @@ -77,7 +76,7 @@ :host "127.0.0.1" :port 6379 :db 15 - :clients 4 + :clients 1 :requests 10000)) (defn create-clients [options] @@ -103,9 +102,14 @@ (defn report-request-times [clients requests] (let [requests-dist (map #(let [perc (* 100 (/ (last %) requests))] (conj % perc)) (requests-by-ms clients))] - (dorun - (map #(println (format "%.2f%% < %d ms" (float (last %)) (inc (first %)))) - requests-dist)))) + (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] @@ -128,7 +132,7 @@ (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) + (report-request-times clients requests) ;(dorun (map report-client-rps clients)) (println (format "%f requests per second\n\n" requests-per-second)) ) diff --git a/client-libraries/clojure/benchmarks/ruby.clj b/client-libraries/clojure/benchmarks/ruby.clj index 0ede54d47..c73c1a53e 100644 --- a/client-libraries/clojure/benchmarks/ruby.clj +++ b/client-libraries/clojure/benchmarks/ruby.clj @@ -1,10 +1,9 @@ (ns benchmarks.ruby (:require redis)) - -(dotimes [n 2] +(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] diff --git a/client-libraries/clojure/build.xml b/client-libraries/clojure/build.xml index b10bdeb00..d25eba38c 100644 --- a/client-libraries/clojure/build.xml +++ b/client-libraries/clojure/build.xml @@ -38,8 +38,8 @@ - - + + @@ -65,6 +65,7 @@ + diff --git a/client-libraries/clojure/src/redis.clj b/client-libraries/clojure/src/redis.clj index 0ec33ba63..cb5a4faed 100644 --- a/client-libraries/clojure/src/redis.clj +++ b/client-libraries/clojure/src/redis.clj @@ -1,11 +1,11 @@ ;(add-classpath "file:///Users/ragge/Projects/clojure/redis-clojure/src/") -(set! *warn-on-reflection* true) - (ns redis (:refer-clojure :exclude [get set type keys sort]) (:use redis.internal)) +;(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. @@ -100,6 +100,7 @@ ;; 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) @@ -122,6 +123,7 @@ (bgsave [] :inline) (lastsave [] :inline int-to-date) (shutdown [] :inline) + ;; Remote control (info [] :inline string-to-map) ;;(monitor [] :inline)) ) diff --git a/client-libraries/clojure/src/redis/internal.clj b/client-libraries/clojure/src/redis/internal.clj index d363a58d0..d8399bb82 100644 --- a/client-libraries/clojure/src/redis/internal.clj +++ b/client-libraries/clojure/src/redis/internal.clj @@ -39,7 +39,8 @@ (let [{:keys [host port timeout]} server socket (Socket. #^String host #^Integer port)] (doto socket - (.setTcpNoDelay true)))) + (.setTcpNoDelay true) + (.setKeepAlive true)))) (defn with-server* [server-spec func] @@ -99,8 +100,7 @@ (defn reply-type ([#^BufferedReader reader] - (let [type (char (.read reader))] - type))) + (char (.read reader)))) (defmulti parse-reply reply-type :default :unknown) diff --git a/client-libraries/clojure/src/redis/tests.clj b/client-libraries/clojure/src/redis/tests.clj index 8add90af6..070b45be7 100644 --- a/client-libraries/clojure/src/redis/tests.clj +++ b/client-libraries/clojure/src/redis/tests.clj @@ -214,20 +214,25 @@ (redis/lset "list" -1 "test3") (is (= "test3" (redis/lindex "list" 2)))) - -;; TBD (deftest lrem (is (thrown? Exception (redis/lrem "foo" 0 "bar"))) - (is (= 0 (redis/lrem "list" 0 "")))) + (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 (= "one" (redis/lpop "list")))) + (is (= nil (redis/lpop "newlist"))) + (is (= "one" (redis/lpop "list"))) + (is (= 2 (redis/llen "list")))) (deftest rpop (is (thrown? Exception (redis/rpop "foo"))) - (is (= "three" (redis/rpop "list")))) + (is (= nil (redis/rpop "newlist"))) + (is (= "three" (redis/rpop "list"))) + (is (= 2 (redis/llen "list")))) ;; ;; Set commands @@ -242,11 +247,16 @@ (deftest srem (is (thrown? Exception (redis/srem "foo" "bar"))) - (is (thrown? Exception (redis/srem "newset" "member"))) + (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"))) diff --git a/client-libraries/clojure/src/redis/tests/internal.clj b/client-libraries/clojure/src/redis/tests/internal.clj index 3daf98dbf..9f1422e96 100644 --- a/client-libraries/clojure/src/redis/tests/internal.clj +++ b/client-libraries/clojure/src/redis/tests/internal.clj @@ -4,11 +4,6 @@ (:import [java.io StringReader BufferedReader])) - - - - - ;; ;; Helpers ;; diff --git a/client-libraries/cpp/TODO b/client-libraries/cpp/TODO index 3e84754d8..cc541cdb0 100644 --- a/client-libraries/cpp/TODO +++ b/client-libraries/cpp/TODO @@ -1,6 +1,12 @@ +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 diff --git a/client-libraries/cpp/redisclient.cpp b/client-libraries/cpp/redisclient.cpp index beaa876a0..5e3468ccc 100644 --- a/client-libraries/cpp/redisclient.cpp +++ b/client-libraries/cpp/redisclient.cpp @@ -40,7 +40,6 @@ #endif #include -#include #include #include @@ -236,32 +235,15 @@ namespace return rtrim(line, CRLF); } - unsigned long unsigned_number_from_string(const string & data) + template + T value_from_string(const string & data) { - errno = 0; + T value; - unsigned long value = strtoul(data.c_str(), NULL, 10); - - if (value == ULONG_MAX && errno == ERANGE) - throw redis::value_error("invalid number; out of range of long"); - - if (value == 0 && errno == EINVAL) - throw redis::value_error("invalid number; unrecognized format"); - - return value; - } - - redis::client::int_type number_from_string(const string & data) - { - errno = 0; - - redis::client::int_type value = strtol(data.c_str(), NULL, 10); - - if ((value == LONG_MAX || value == LONG_MIN) && errno == ERANGE) - throw redis::value_error("invalid number; out of range of long"); - - if (value == 0 && errno == EINVAL) - throw redis::value_error("invalid number; unrecognized format"); + istringstream iss(data); + iss >> value; + if (iss.fail()) + throw redis::value_error("invalid number"); return value; } @@ -284,6 +266,10 @@ namespace 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 @@ -578,11 +564,11 @@ namespace redis return recv_multi_bulk_reply_(out); } - void client::sinterstore(const client::string_type & dstkey, - const client::string_vector & keys) + client::int_type client::sinterstore(const client::string_type & dstkey, + const client::string_vector & keys) { send_(makecmd("SINTERSTORE") << dstkey << ' ' << keys); - recv_ok_reply_(); + return recv_int_reply_(); } client::int_type client::sunion(const client::string_vector & keys, @@ -592,11 +578,11 @@ namespace redis return recv_multi_bulk_reply_(out); } - void client::sunionstore(const client::string_type & dstkey, - const client::string_vector & keys) + client::int_type client::sunionstore(const client::string_type & dstkey, + const client::string_vector & keys) { send_(makecmd("SUNIONSTORE") << dstkey << ' ' << keys); - recv_ok_reply_(); + return recv_int_reply_(); } client::int_type client::smembers(const client::string_type & key, @@ -746,25 +732,27 @@ namespace redis if (key == server_info_key_version) out.version = val; else if (key == server_info_key_bgsave_in_progress) - out.bgsave_in_progress = unsigned_number_from_string(val) == 1; + out.bgsave_in_progress = value_from_string(val) == 1; else if (key == server_info_key_connected_clients) - out.connected_clients = unsigned_number_from_string(val); + out.connected_clients = value_from_string(val); else if (key == server_info_key_connected_slaves) - out.connected_slaves = unsigned_number_from_string(val); + out.connected_slaves = value_from_string(val); else if (key == server_info_key_used_memory) - out.used_memory = unsigned_number_from_string(val); + out.used_memory = value_from_string(val); else if (key == server_info_key_changes_since_last_save) - out.changes_since_last_save = unsigned_number_from_string(val); + out.changes_since_last_save = value_from_string(val); else if (key == server_info_key_last_save_time) - out.last_save_time = unsigned_number_from_string(val); + out.last_save_time = value_from_string(val); else if (key == server_info_key_total_connections_received) - out.total_connections_received = unsigned_number_from_string(val); + out.total_connections_received = value_from_string(val); else if (key == server_info_key_total_commands_processed) - out.total_commands_processed = unsigned_number_from_string(val); + out.total_commands_processed = value_from_string(val); else if (key == server_info_key_uptime_in_seconds) - out.uptime_in_seconds = unsigned_number_from_string(val); + out.uptime_in_seconds = value_from_string(val); else if (key == server_info_key_uptime_in_days) - out.uptime_in_days = unsigned_number_from_string(val); + out.uptime_in_days = value_from_string(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 + "'"); } @@ -826,7 +814,7 @@ namespace redis if (line[0] != prefix) throw protocol_error("unexpected prefix for bulk reply"); - return number_from_string(line.substr(1)); + return value_from_string(line.substr(1)); } string client::recv_bulk_reply_() @@ -895,7 +883,7 @@ namespace redis if (line[0] != prefix_int_reply) throw protocol_error("unexpected prefix for integer reply"); - return number_from_string(line.substr(1)); + return value_from_string(line.substr(1)); } void client::recv_int_ok_reply_() diff --git a/client-libraries/cpp/redisclient.h b/client-libraries/cpp/redisclient.h index d3fee780a..b51da8f95 100644 --- a/client-libraries/cpp/redisclient.h +++ b/client-libraries/cpp/redisclient.h @@ -39,6 +39,12 @@ namespace redis { + enum server_role + { + role_master, + role_slave + }; + struct server_info { std::string version; @@ -52,6 +58,7 @@ namespace redis 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. @@ -355,8 +362,9 @@ namespace redis // 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 - void sinterstore(const string_type & dstkey, const string_vector & keys); + int_type sinterstore(const string_type & dstkey, const string_vector & keys); // Return the union between the sets stored at key1, key2, ..., keyN @@ -364,8 +372,9 @@ namespace redis // 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 - void sunionstore(const string_type & dstkey, const string_vector & keys); + int_type sunionstore(const string_type & dstkey, const string_vector & keys); // Return all the members of the set value at key diff --git a/client-libraries/cpp/test_client.cpp b/client-libraries/cpp/test_client.cpp index c0891cb0a..53ef5117a 100644 --- a/client-libraries/cpp/test_client.cpp +++ b/client-libraries/cpp/test_client.cpp @@ -478,7 +478,7 @@ int main(int argc, char ** argv) keys.push_back("seta"); keys.push_back("setb"); - c.sinterstore("setc", keys); + ASSERT_EQUAL(c.sinterstore("setc", keys), 2L); redis::client::string_set members; ASSERT_EQUAL(c.smembers("setc", members), 2L); @@ -510,7 +510,7 @@ int main(int argc, char ** argv) keys.push_back("setf"); keys.push_back("setg"); - c.sunionstore("seth", keys); + ASSERT_EQUAL(c.sunionstore("seth", keys), 2L); redis::client::string_set members; ASSERT_EQUAL(c.smembers("seth", members), 2L); diff --git a/client-libraries/lua/redis.lua b/client-libraries/lua/redis.lua index 87351745a..1e85ffd87 100644 --- a/client-libraries/lua/redis.lua +++ b/client-libraries/lua/redis.lua @@ -239,6 +239,7 @@ redis_commands = { 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'), @@ -262,6 +263,7 @@ redis_commands = { 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'), @@ -276,12 +278,17 @@ redis_commands = { pop_last = inline('RPOP'), -- commands operating on sets - set_add = inline('SADD'), - set_remove = inline('SREM'), + 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 @@ -321,7 +328,7 @@ redis_commands = { ), -- remote server control commands - info = inline('INFO', + info = inline('INFO', function(response) local info = {} response:gsub('([^\r\n]*)\r\n', function(kv) @@ -331,4 +338,10 @@ redis_commands = { return info end ), + slave_of = inline('SLAVEOF'), + slave_of_no_one = custom('SLAVEOF', + function(client, command) + return request.inline(client, command, 'NO ONE') + end + ), } diff --git a/client-libraries/ruby/.gitignore b/client-libraries/ruby/.gitignore index 2fd676b3c..f3b05636d 100644 --- a/client-libraries/ruby/.gitignore +++ b/client-libraries/ruby/.gitignore @@ -2,4 +2,5 @@ nohup.out redis/* rdsrv pkg/* +coverage/* .idea diff --git a/client-libraries/ruby/Rakefile b/client-libraries/ruby/Rakefile index 9bed311a3..b6217054a 100644 --- a/client-libraries/ruby/Rakefile +++ b/client-libraries/ruby/Rakefile @@ -8,7 +8,7 @@ require 'tasks/redis.tasks' GEM = 'redis' GEM_NAME = 'redis' -GEM_VERSION = '0.0.3.4' +GEM_VERSION = '0.1' AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark'] EMAIL = "ez@engineyard.com" HOMEPAGE = "http://github.com/ezmobius/redis-rb" @@ -53,4 +53,10 @@ task :make_spec do File.open("#{GEM}.gemspec", "w") do |file| file.puts spec.to_ruby end -end \ No newline at end of file +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 diff --git a/client-libraries/ruby/spec/redis_spec.rb b/client-libraries/ruby/spec/redis_spec.rb index d4dde9641..4ffa6b669 100644 --- a/client-libraries/ruby/spec/redis_spec.rb +++ b/client-libraries/ruby/spec/redis_spec.rb @@ -29,7 +29,11 @@ describe "redis" do @r.quit end - it 'should be able to PING' do + it "should be able connect without a timeout" do + lambda { Redis.new :timeout => 0 }.should_not raise_error + end + + it "should be able to PING" do @r.ping.should == 'PONG' end @@ -61,13 +65,23 @@ describe "redis" do 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') @@ -75,7 +89,14 @@ describe "redis" do @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 @@ -137,6 +158,10 @@ describe "redis" do @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 #BTM - TODO it "should be able to check the TYPE of a key" do @r['foo'] = 'nik' @@ -356,20 +381,24 @@ describe "redis" do @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1], :order => 'desc alpha').should == ['taj', 'terrier'] end # - it "should provide info" do + 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" do + 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 @@ -387,6 +416,10 @@ describe "redis" do @r.bgsave.should == 'OK' end + it "should should be able to ECHO" do + @r.echo("message in a bottle\n").should == "message in a bottle\n" + end + it "should handle multiple servers" do require 'dist_redis' @r = DistRedis.new(:hosts=> ['localhost:6379', '127.0.0.1:6379'], :db => 15) diff --git a/client-libraries/update-clojure-client.sh b/client-libraries/update-clojure-client.sh new file mode 100755 index 000000000..125ad2caa --- /dev/null +++ b/client-libraries/update-clojure-client.sh @@ -0,0 +1,12 @@ +#!/bin/sh +rm -rf temp +mkdir temp +cd temp +git clone git://github.com/ragnard/redis-clojure.git +cd redis-clojure +rm -rf .git +cd .. +cd .. +rm -rf clojure +mv temp/redis-clojure clojure +rm -rf temp