summaryrefslogtreecommitdiff
path: root/client-libraries
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2009-05-26 18:10:50 +0200
committerantirez <antirez@gmail.com>2009-05-26 18:10:50 +0200
commitd7fc9edb182d361ac5c31b21fcdd25345c972898 (patch)
treedfd98d0c2e5a8d59756071a9568b52a2bfad8e62 /client-libraries
parente083d7526209d0faf2ac0fabbc1a1e59b9974ce2 (diff)
downloadredis-d7fc9edb182d361ac5c31b21fcdd25345c972898.tar.gz
client libraries synched in git
Diffstat (limited to 'client-libraries')
-rw-r--r--client-libraries/cpp/TODO21
-rw-r--r--client-libraries/cpp/redisclient.cpp24
-rw-r--r--client-libraries/cpp/redisclient.h2
-rw-r--r--client-libraries/lua/redis.lua2
-rw-r--r--client-libraries/ruby/.gitignore3
-rw-r--r--client-libraries/ruby/README.markdown7
-rw-r--r--client-libraries/ruby/Rakefile11
-rw-r--r--client-libraries/ruby/benchmarking/suite.rb24
-rw-r--r--client-libraries/ruby/benchmarking/worker.rb71
-rw-r--r--client-libraries/ruby/lib/pipeline.rb9
-rw-r--r--client-libraries/ruby/lib/redis.rb334
-rw-r--r--client-libraries/ruby/lib/server.rb67
-rw-r--r--client-libraries/ruby/redis-rb.gemspec2
-rw-r--r--client-libraries/ruby/spec/redis_spec.rb83
-rw-r--r--client-libraries/ruby/spec/server_spec.rb22
-rw-r--r--client-libraries/ruby/tasks/redis.tasks.rb2
16 files changed, 380 insertions, 304 deletions
diff --git a/client-libraries/cpp/TODO b/client-libraries/cpp/TODO
index 55967cf40..3e84754d8 100644
--- a/client-libraries/cpp/TODO
+++ b/client-libraries/cpp/TODO
@@ -1,9 +1,16 @@
-+ finish command implementations
-= finish unit tests
- Only a few left, to test the SORT command's edge cases (e.g. BY pattern)
-+ determine if we should not use bool return values and instead throw redis_error. (latter).
-+ maybe more fine-grained exceptions (not just redis_error but operation_not_permitted_error, etc.)
-- benchmarking
+command handlers:
+- support DEL as vararg
+- support MLLEN and MSCARD
+
+unit tests:
+- sort with limit
+- sort lexicographically
+- sort with pattern and weights
+
+extras:
+- benchmarking "test" app
- consistent hashing?
-- make all string literals constants so they can be easily changed (minor)
+
+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)
diff --git a/client-libraries/cpp/redisclient.cpp b/client-libraries/cpp/redisclient.cpp
index 75e6e8781..beaa876a0 100644
--- a/client-libraries/cpp/redisclient.cpp
+++ b/client-libraries/cpp/redisclient.cpp
@@ -663,16 +663,24 @@ namespace redis
const client::string_type & by_pattern,
client::int_type limit_start,
client::int_type limit_end,
- const client::string_type & get_pattern,
+ const client::string_vector & get_patterns,
client::sort_order order,
bool lexicographically)
{
- send_(makecmd("SORT") << key
- << " BY " << by_pattern
- << " LIMIT " << limit_start << ' ' << limit_end
- << " GET " << get_pattern
- << (order == sort_order_ascending ? " ASC" : " DESC")
- << (lexicographically ? " ALPHA" : ""));
+ 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);
}
@@ -681,7 +689,7 @@ namespace redis
{
send_(makecmd("SAVE", true));
recv_ok_reply_();
- e.g. }
+ }
void client::bgsave()
{
diff --git a/client-libraries/cpp/redisclient.h b/client-libraries/cpp/redisclient.h
index b1fbb582f..d3fee780a 100644
--- a/client-libraries/cpp/redisclient.h
+++ b/client-libraries/cpp/redisclient.h
@@ -421,7 +421,7 @@ namespace redis
const string_type & by_pattern,
int_type limit_start,
int_type limit_end,
- const string_type & get_pattern,
+ const string_vector & get_patterns,
sort_order order = sort_order_ascending,
bool lexicographically = false);
diff --git a/client-libraries/lua/redis.lua b/client-libraries/lua/redis.lua
index 2455ceb3d..87351745a 100644
--- a/client-libraries/lua/redis.lua
+++ b/client-libraries/lua/redis.lua
@@ -316,7 +316,7 @@ redis_commands = {
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(command .. protocol.newline)
+ network.write(client, command .. protocol.newline)
end
),
diff --git a/client-libraries/ruby/.gitignore b/client-libraries/ruby/.gitignore
index 10d0977b0..2fd676b3c 100644
--- a/client-libraries/ruby/.gitignore
+++ b/client-libraries/ruby/.gitignore
@@ -1,4 +1,5 @@
nohup.out
redis/*
rdsrv
-pkg/* \ No newline at end of file
+pkg/*
+.idea
diff --git a/client-libraries/ruby/README.markdown b/client-libraries/ruby/README.markdown
index 2518c4212..b36633d62 100644
--- a/client-libraries/ruby/README.markdown
+++ b/client-libraries/ruby/README.markdown
@@ -12,7 +12,10 @@ See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for m
## Dependencies
-1. redis -
+1. rspec -
+ sudo gem install rspec
+
+2. redis -
rake redis:install
@@ -20,7 +23,7 @@ See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for m
rake dtach:install
-3. svn - git is the new black, but we need it for the google codes.
+3. git - git is the new black.
## Setup
diff --git a/client-libraries/ruby/Rakefile b/client-libraries/ruby/Rakefile
index bdc9f373b..9bed311a3 100644
--- a/client-libraries/ruby/Rakefile
+++ b/client-libraries/ruby/Rakefile
@@ -8,10 +8,10 @@ require 'tasks/redis.tasks'
GEM = 'redis'
GEM_NAME = 'redis'
-GEM_VERSION = '0.0.3.3'
+GEM_VERSION = '0.0.3.4'
AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark']
-EMAIL = "matt.clark@punchstock.com"
-HOMEPAGE = "http://github.com/winescout/redis-rb"
+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|
@@ -25,10 +25,7 @@ spec = Gem::Specification.new do |s|
s.authors = AUTHORS
s.email = EMAIL
s.homepage = HOMEPAGE
-
- # Uncomment this to add a dependency
- # s.add_dependency "foo"
-
+ s.add_dependency "rspec"
s.require_path = 'lib'
s.autorequire = GEM
s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,spec}/**/*")
diff --git a/client-libraries/ruby/benchmarking/suite.rb b/client-libraries/ruby/benchmarking/suite.rb
new file mode 100644
index 000000000..f03694b0e
--- /dev/null
+++ b/client-libraries/ruby/benchmarking/suite.rb
@@ -0,0 +1,24 @@
+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 \ No newline at end of file
diff --git a/client-libraries/ruby/benchmarking/worker.rb b/client-libraries/ruby/benchmarking/worker.rb
new file mode 100644
index 000000000..836d03b06
--- /dev/null
+++ b/client-libraries/ruby/benchmarking/worker.rb
@@ -0,0 +1,71 @@
+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
+
diff --git a/client-libraries/ruby/lib/pipeline.rb b/client-libraries/ruby/lib/pipeline.rb
index deaedd159..f92b96db5 100644
--- a/client-libraries/ruby/lib/pipeline.rb
+++ b/client-libraries/ruby/lib/pipeline.rb
@@ -9,10 +9,7 @@ class Redis
@commands = []
end
- def get_response
- end
-
- def write(data)
+ def execute_command(data)
@commands << data
write_and_read if @commands.size >= BUFFER_SIZE
end
@@ -22,10 +19,10 @@ class Redis
end
def write_and_read
- @redis.write @commands.join
+ @redis.execute_command(@commands.join, true)
@redis.read_socket
@commands.clear
end
end
-end \ No newline at end of file
+end
diff --git a/client-libraries/ruby/lib/redis.rb b/client-libraries/ruby/lib/redis.rb
index b27918bd4..b10c42bf3 100644
--- a/client-libraries/ruby/lib/redis.rb
+++ b/client-libraries/ruby/lib/redis.rb
@@ -3,14 +3,15 @@ require 'set'
require File.join(File.dirname(__FILE__),'server')
require File.join(File.dirname(__FILE__),'pipeline')
-
class RedisError < StandardError
end
class RedisRenameError < StandardError
end
+
class Redis
ERR = "-".freeze
OK = 'OK'.freeze
+ PONG = 'PONG'.freeze
SINGLE = '+'.freeze
BULK = '$'.freeze
MULTI = '*'.freeze
@@ -18,22 +19,21 @@ class Redis
attr_reader :server
-
def initialize(opts={})
@opts = {:host => 'localhost', :port => '6379', :db => 0}.merge(opts)
$debug = @opts[:debug]
@db = @opts[:db]
@server = Server.new(@opts[:host], @opts[:port], (@opts[:timeout]||10))
end
-
+
def pipelined
pipeline = Pipeline.new(self)
yield pipeline
pipeline.finish
end
-
+
def to_s
- "#{host}:#{port}"
+ "#{host}:#{port} -> #{@db}"
end
def port
@@ -44,76 +44,42 @@ class Redis
@opts[:host]
end
- def with_socket_management(server, &block)
- begin
- socket = server.socket
- block.call(socket)
- #Timeout or server down
- rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED, Timeout::Error => e
- server.close
- puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug
- retry
- #Server down
- rescue NoMethodError => e
- puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug
- raise Errno::ECONNREFUSED
- #exit
- end
+ def quit
+ execute_command("QUIT\r\n", true)
end
- def monitor
- with_socket_management(@server) do |socket|
- trap("INT") { puts "\nGot ^C! Dying!"; exit }
- write "MONITOR\r\n"
- puts "Now Monitoring..."
- socket.read(12)
- loop do
- x = socket.gets
- puts x unless x.nil?
- end
- end
+ def ping
+ execute_command("PING\r\n") == PONG
end
- def quit
- write "QUIT\r\n"
- end
-
def select_db(index)
@db = index
- write "SELECT #{index}\r\n"
- get_response
+ execute_command("SELECT #{index}\r\n")
end
def flush_db
- write "FLUSHDB\r\n"
- get_response == OK
+ execute_command("FLUSHDB\r\n") == OK
end
def flush_all
- ensure_retry do
- puts "Warning!\nFlushing *ALL* databases!\n5 Seconds to Hit ^C!"
- trap('INT') {quit; return false}
- sleep 5
- write "FLUSHALL\r\n"
- get_response == OK
- end
+ puts "Warning!\nFlushing *ALL* databases!\n5 Seconds to Hit ^C!"
+ trap('INT') {quit; return false}
+ sleep 5
+ execute_command("FLUSHALL\r\n") == OK
end
def last_save
- write "LASTSAVE\r\n"
- get_response.to_i
+ execute_command("LASTSAVE\r\n").to_i
end
def bgsave
- write "BGSAVE\r\n"
- get_response == OK
+ execute_command("BGSAVE\r\n") == OK
end
def info
info = {}
- write("INFO\r\n")
- x = get_response
- x.each do |kv|
+ x = execute_command("INFO\r\n")
+ x.each_line do |kv|
k,v = kv.split(':', 2)
k,v = k.chomp, v = v.chomp
info[k.to_sym] = v
@@ -121,55 +87,16 @@ class Redis
info
end
-
- def bulk_reply
- begin
- x = read
- puts "bulk_reply read value is #{x.inspect}" if $debug
- return x
- rescue => e
- puts "error in bulk_reply #{e}" if $debug
- nil
- end
- end
-
- def write(data)
- with_socket_management(@server) do |socket|
- puts "writing: #{data}" if $debug
- socket.write(data)
- end
- end
-
- def fetch(len)
- with_socket_management(@server) do |socket|
- len = [0, len.to_i].max
- res = socket.read(len + 2)
- res = res.chomp if res
- res
- end
- end
-
- def read(length = read_proto)
- with_socket_management(@server) do |socket|
- res = socket.read(length)
- puts "read is #{res.inspect}" if $debug
- res
- end
- end
-
def keys(glob)
- write "KEYS #{glob}\r\n"
- get_response.split(' ')
+ execute_command("KEYS #{glob}\r\n").split(' ')
end
def rename!(oldkey, newkey)
- write "RENAME #{oldkey} #{newkey}\r\n"
- get_response
+ execute_command("RENAME #{oldkey} #{newkey}\r\n")
end
def rename(oldkey, newkey)
- write "RENAMENX #{oldkey} #{newkey}\r\n"
- case get_response
+ case execute_command("RENAMENX #{oldkey} #{newkey}\r\n")
when -1
raise RedisRenameError, "source key: #{oldkey} does not exist"
when 0
@@ -182,13 +109,11 @@ class Redis
end
def key?(key)
- write "EXISTS #{key}\r\n"
- get_response == 1
+ execute_command("EXISTS #{key}\r\n") == 1
end
def delete(key)
- write "DEL #{key}\r\n"
- get_response == 1
+ execute_command("DEL #{key}\r\n") == 1
end
def [](key)
@@ -196,41 +121,35 @@ class Redis
end
def get(key)
- write "GET #{key}\r\n"
- get_response
+ execute_command("GET #{key}\r\n")
end
def mget(*keys)
- write "MGET #{keys.join(' ')}\r\n"
- get_response
+ execute_command("MGET #{keys.join(' ')}\r\n")
end
def incr(key, increment=nil)
if increment
- write "INCRBY #{key} #{increment}\r\n"
+ execute_command("INCRBY #{key} #{increment}\r\n")
else
- write "INCR #{key}\r\n"
+ execute_command("INCR #{key}\r\n")
end
- get_response
end
def decr(key, decrement=nil)
if decrement
- write "DECRBY #{key} #{decrement}\r\n"
+ execute_command("DECRBY #{key} #{decrement}\r\n")
else
- write "DECR #{key}\r\n"
+ execute_command("DECR #{key}\r\n")
end
- get_response
end
def randkey
- write "RANDOMKEY\r\n"
- get_response
+ execute_command("RANDOMKEY\r\n")
end
def list_length(key)
- write "LLEN #{key}\r\n"
- case i = get_response
+ case i = execute_command("LLEN #{key}\r\n")
when -2
raise RedisError, "key: #{key} does not hold a list value"
else
@@ -239,53 +158,43 @@ class Redis
end
def type?(key)
- write "TYPE #{key}\r\n"
- get_response
+ execute_command("TYPE #{key}\r\n")
end
- def push_tail(key, string)
- write "RPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
- get_response
+ def push_tail(key, val)
+ execute_command("RPUSH #{key} #{value_to_wire(val)}\r\n")
end
- def push_head(key, string)
- write "LPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
- get_response
+ def push_head(key, val)
+ execute_command("LPUSH #{key} #{value_to_wire(val)}\r\n")
end
def pop_head(key)
- write "LPOP #{key}\r\n"
- get_response
+ execute_command("LPOP #{key}\r\n")
end
def pop_tail(key)
- write "RPOP #{key}\r\n"
- get_response
+ execute_command("RPOP #{key}\r\n")
end
def list_set(key, index, val)
- write "LSET #{key} #{index} #{val.to_s.size}\r\n#{val}\r\n"
- get_response == OK
+ execute_command("LSET #{key} #{index} #{value_to_wire(val)}\r\n") == OK
end
def list_range(key, start, ending)
- write "LRANGE #{key} #{start} #{ending}\r\n"
- get_response
+ execute_command("LRANGE #{key} #{start} #{ending}\r\n")
end
def list_trim(key, start, ending)
- write "LTRIM #{key} #{start} #{ending}\r\n"
- get_response
+ execute_command("LTRIM #{key} #{start} #{ending}\r\n")
end
def list_index(key, index)
- write "LINDEX #{key} #{index}\r\n"
- get_response
+ execute_command("LINDEX #{key} #{index}\r\n")
end
- def list_rm(key, count, value)
- write "LREM #{key} #{count} #{value.to_s.size}\r\n#{value}\r\n"
- case num = get_response
+ def list_rm(key, count, val)
+ case num = execute_command("LREM #{key} #{count} #{value_to_wire(val)}\r\n")
when -1
raise RedisError, "key: #{key} does not exist"
when -2
@@ -296,8 +205,7 @@ class Redis
end
def set_add(key, member)
- write "SADD #{key} #{member.to_s.size}\r\n#{member}\r\n"
- case get_response
+ case execute_command("SADD #{key} #{value_to_wire(member)}\r\n")
when 1
true
when 0
@@ -308,8 +216,7 @@ class Redis
end
def set_delete(key, member)
- write "SREM #{key} #{member.to_s.size}\r\n#{member}\r\n"
- case get_response
+ case execute_command("SREM #{key} #{value_to_wire(member)}\r\n")
when 1
true
when 0
@@ -320,8 +227,7 @@ class Redis
end
def set_count(key)
- write "SCARD #{key}\r\n"
- case i = get_response
+ case i = execute_command("SCARD #{key}\r\n")
when -2
raise RedisError, "key: #{key} contains a non set value"
else
@@ -330,8 +236,7 @@ class Redis
end
def set_member?(key, member)
- write "SISMEMBER #{key} #{member.to_s.size}\r\n#{member}\r\n"
- case get_response
+ case execute_command("SISMEMBER #{key} #{value_to_wire(member)}\r\n")
when 1
true
when 0
@@ -342,57 +247,93 @@ class Redis
end
def set_members(key)
- write "SMEMBERS #{key}\r\n"
- Set.new(get_response)
+ Set.new(execute_command("SMEMBERS #{key}\r\n"))
end
def set_intersect(*keys)
- write "SINTER #{keys.join(' ')}\r\n"
- Set.new(get_response)
+ Set.new(execute_command("SINTER #{keys.join(' ')}\r\n"))
end
def set_inter_store(destkey, *keys)
- write "SINTERSTORE #{destkey} #{keys.join(' ')}\r\n"
- get_response
+ execute_command("SINTERSTORE #{destkey} #{keys.join(' ')}\r\n")
end
def set_union(*keys)
- write "SUNION #{keys.join(' ')}\r\n"
- Set.new(get_response)
+ Set.new(execute_command("SUNION #{keys.join(' ')}\r\n"))
end
def set_union_store(destkey, *keys)
- write "SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n"
- get_response
+ execute_command("SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n")
end
def set_diff(*keys)
- write "SDIFF #{keys.join(' ')}\r\n"
- Set.new(get_response)
+ Set.new(execute_command("SDIFF #{keys.join(' ')}\r\n"))
end
def set_diff_store(destkey, *keys)
- write "SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n"
- get_response
+ execute_command("SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n")
end
def set_move(srckey, destkey, member)
- write "SMOVE #{srckey} #{destkey} #{member.to_s.size}\r\n#{member}\r\n"
- get_response == 1
+ execute_command("SMOVE #{srckey} #{destkey} #{value_to_wire(member)}\r\n") == 1
end
def sort(key, opts={})
cmd = "SORT #{key}"
cmd << " BY #{opts[:by]}" if opts[:by]
- cmd << " GET #{opts[:get]}" if opts[:get]
+ cmd << " GET #{[opts[:get]].flatten * ' GET '}" if opts[:get]
cmd << " INCR #{opts[:incr]}" if opts[:incr]
cmd << " DEL #{opts[:del]}" if opts[:del]
cmd << " DECR #{opts[:decr]}" if opts[:decr]
cmd << " #{opts[:order]}" if opts[:order]
cmd << " LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
cmd << "\r\n"
- write(cmd)
- get_response
+ execute_command(cmd)
+ end
+
+ def []=(key, val)
+ set(key,val)
+ end
+
+ def set(key, val, expiry=nil)
+ s = execute_command("SET #{key} #{value_to_wire(val)}\r\n") == OK
+ return expire(key, expiry) if s && expiry
+ s
+ end
+
+ def dbsize
+ execute_command("DBSIZE\r\n")
+ end
+
+ def expire(key, expiry=nil)
+ execute_command("EXPIRE #{key} #{expiry}\r\n") == 1
+ end
+
+ def set_unless_exists(key, val)
+ execute_command("SETNX #{key} #{value_to_wire(val)}\r\n") == 1
+ end
+
+ def bulk_reply
+ begin
+ x = read
+ puts "bulk_reply read value is #{x.inspect}" if $debug
+ return x
+ rescue => e
+ puts "error in bulk_reply #{e}" if $debug
+ nil
+ end
+ end
+
+ def write(data)
+ puts "writing: #{data}" if $debug
+ @socket.write(data)
+ end
+
+ def read(length = 0)
+ length = read_proto unless length > 0
+ res = @socket.read(length)
+ puts "read is #{res.inspect}" if $debug
+ res
end
def multi_bulk
@@ -418,28 +359,6 @@ class Redis
r
end
- def []=(key, val)
- set(key,val)
- end
-
-
- def set(key, val, expiry=nil)
- write("SET #{key} #{val.to_s.size}\r\n#{val}\r\n")
- s = get_response == OK
- return expire(key, expiry) if s && expiry
- s
- end
-
- def expire(key, expiry=nil)
- write("EXPIRE #{key} #{expiry}\r\n")
- get_response == 1
- end
-
- def set_unless_exists(key, val)
- write "SETNX #{key} #{val.to_s.size}\r\n#{val}\r\n"
- get_response == 1
- end
-
def status_code_reply
begin
res = read_proto
@@ -452,13 +371,26 @@ class Redis
raise RedisError
end
end
-
+
+ def execute_command(command, ignore_response=false)
+ ss = server.socket
+ unless ss.object_id == @socket.object_id
+ @socket = ss
+ puts "Socket changed, selecting DB" if $debug
+ unless command[0..6] == 'SELECT'
+ #BTM - Ugh- DRY but better than infinite recursion
+ write("SELECT #{@db}\r\n")
+ get_response
+ end
+ end
+ write(command)
+ get_response unless ignore_response
+ rescue Errno::ECONNRESET, Errno::EPIPE, NoMethodError, Timeout::Error => e
+ raise RedisError, "Connection error"
+ end
+
def get_response
- begin
- rtype = get_reply
- rescue => e
- raise RedisError, e.inspect
- end
+ rtype = get_reply
puts "reply_type is #{rtype.inspect}" if $debug
case rtype
when SINGLE
@@ -512,13 +444,21 @@ class Redis
end
def read_proto
- with_socket_management(@server) do |socket|
- if res = socket.gets
- x = res.chomp
- puts "read_proto is #{x.inspect}\n\n" if $debug
- x.to_i
- end
+ res = @socket.readline
+ x = res.chomp
+ puts "read_proto is #{x.inspect}\n\n" if $debug
+ x.to_i
+ end
+
+ private
+ def value_to_wire(value)
+ value_str = value.to_s
+ if value_str.respond_to?(:bytesize)
+ value_size = value_str.bytesize
+ else
+ value_size = value_str.size
end
+ "#{value_size}\r\n#{value_str}"
end
end
diff --git a/client-libraries/ruby/lib/server.rb b/client-libraries/ruby/lib/server.rb
index c5ac808c0..4fb54937f 100644
--- a/client-libraries/ruby/lib/server.rb
+++ b/client-libraries/ruby/lib/server.rb
@@ -25,12 +25,6 @@ end
class Server
##
- # The amount of time to wait before attempting to re-establish a
- # connection with a server that is marked dead.
-
- RETRY_DELAY = 30.0
-
- ##
# The host the redis server is running on.
attr_reader :host
@@ -41,16 +35,6 @@ class Server
attr_reader :port
##
- #
-
- attr_reader :replica
-
- ##
- # The time of next retry if the connection is dead.
-
- attr_reader :retry
-
- ##
# A text status string describing the state of the server.
attr_reader :status
@@ -67,7 +51,6 @@ class Server
@port = port.to_i
@sock = nil
- @retry = nil
@status = 'NOT CONNECTED'
@timeout = timeout
end
@@ -83,38 +66,31 @@ class Server
# Returns the connected socket object on success or nil on failure.
def socket
- return @sock if @sock and not @sock.closed?
-
- @sock = nil
-
- # If the host was dead, don't retry for a while.
- return if @retry and @retry > Time.now
-
+ return @sock if socket_alive?
+ close
# Attempt to connect if not already connected.
begin
@sock = connect_to(@host, @port, @timeout)
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
- @retry = nil
@status = 'CONNECTED'
rescue Errno::EPIPE, Errno::ECONNREFUSED => e
- puts "Socket died... socket: #{@sock.inspect}\n" if $debug
- @sock.close
+ puts "Socket died... : #{e}\n" if $debug
retry
rescue SocketError, SystemCallError, IOError => err
puts "Unable to open socket: #{err.class.name}, #{err.message}" if $debug
- mark_dead err
end
@sock
end
def connect_to(host, port, timeout=nil)
- socket = TCPSocket.new(host, port, 0)
+ socket = TCPSocket.new(host, port)
+ socket.set_encoding(Encoding::BINARY) if socket.respond_to?(:set_encoding)
if timeout
socket.instance_eval <<-EOR
- alias :blocking_gets :gets
- def gets(*args)
+ alias :blocking_readline :readline
+ def readline(*args)
RedisTimer.timeout(#{timeout}) do
- self.blocking_gets(*args)
+ self.blocking_readline(*args)
end
end
alias :blocking_read :read
@@ -134,27 +110,22 @@ class Server
socket
end
- ##
# Close the connection to the redis server targeted by this
- # object. The server is not considered dead.
+ # object.
def close
- @sock.close if @sock && !@sock.closed?
+ @sock.close if !@sock.nil? && !@sock.closed?
@sock = nil
- @retry = nil
@status = "NOT CONNECTED"
end
- ##
- # Mark the server as dead and close its socket.
- def mark_dead(error)
- @sock.close if @sock && !@sock.closed?
- @sock = nil
- @retry = Time.now #+ RETRY_DELAY
-
- reason = "#{error.class.name}: #{error.message}"
- @status = sprintf "%s:%s DEAD (%s), will retry at %s", @host, @port, reason, @retry
- puts @status
- end
-
+ private
+ def socket_alive?
+ #BTM - TODO - FileStat is borked under JRuby
+ unless defined?(JRUBY_VERSION)
+ !@sock.nil? && !@sock.closed? && @sock.stat.readable?
+ else
+ !@sock.nil? && !@sock.closed?
+ end
+ end
end
diff --git a/client-libraries/ruby/redis-rb.gemspec b/client-libraries/ruby/redis-rb.gemspec
index 5e284b301..e8d8b6eb9 100644
--- a/client-libraries/ruby/redis-rb.gemspec
+++ b/client-libraries/ruby/redis-rb.gemspec
@@ -2,7 +2,7 @@
Gem::Specification.new do |s|
s.name = %q{redis}
- s.version = "0.0.3.4"
+ s.version = "0.0.3.5"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Ezra Zygmuntowicz", "Taylor Weibley", "Matthew Clark"]
diff --git a/client-libraries/ruby/spec/redis_spec.rb b/client-libraries/ruby/spec/redis_spec.rb
index 71a63259b..de302f345 100644
--- a/client-libraries/ruby/spec/redis_spec.rb
+++ b/client-libraries/ruby/spec/redis_spec.rb
@@ -13,8 +13,8 @@ end
describe "redis" do
before(:all) do
- @r = Redis.new
- @r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data
+ # use database 15 for testing so we dont accidentally step on you real data
+ @r = Redis.new :db => 15
end
before(:each) do
@@ -29,6 +29,9 @@ describe "redis" do
@r.quit
end
+ it 'should be able to PING' do
+ @r.ping.should == true
+ end
it "should be able to GET a key" do
@r['foo'].should == 'bar'
@@ -102,7 +105,16 @@ describe "redis" do
lambda {@r.rename 'foo', 'bar'}.should raise_error(RedisRenameError)
@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)
@@ -287,6 +299,7 @@ describe "redis" do
@r.set_inter_store('newone', 'set', 'set2').should == 'OK'
@r.set_members('newone').should == Set.new(['key2'])
@r.delete('set')
+ @r.delete('set2')
end
#
it "should be able to do set union" do
@@ -296,6 +309,7 @@ describe "redis" do
@r.set_add "set2", 'key3'
@r.set_union('set', 'set2').should == Set.new(['key1','key2','key3'])
@r.delete('set')
+ @r.delete('set2')
end
#
it "should be able to do set union and store the results in a key" do
@@ -306,28 +320,29 @@ describe "redis" do
@r.set_union_store('newone', 'set', 'set2').should == 'OK'
@r.set_members('newone').should == Set.new(['key1','key2','key3'])
@r.delete('set')
+ @r.delete('set2')
end
-
- # these don't seem to be implemented in redis head?
- # it "should be able to do set difference" do
- # @r.set_add "set", 'key1'
- # @r.set_add "set", 'key2'
- # @r.set_add "set2", 'key2'
- # @r.set_add "set2", 'key3'
- # @r.set_diff('set', 'set2').should == Set.new(['key1','key3'])
- # @r.delete('set')
- # end
- # #
- # it "should be able to do set difference and store the results in a key" do
- # @r.set_add "set", 'key1'
- # @r.set_add "set", 'key2'
- # @r.set_add "set2", 'key2'
- # @r.set_add "set2", 'key3'
- # count = @r.set_diff_store('newone', 'set', 'set2')
- # count.should == 3
- # @r.set_members('newone').should == Set.new(['key1','key3'])
- # @r.delete('set')
- # end
+ #
+ it "should be able to do set difference" do
+ @r.set_add "set", 'a'
+ @r.set_add "set", 'b'
+ @r.set_add "set2", 'b'
+ @r.set_add "set2", 'c'
+ @r.set_diff('set', 'set2').should == Set.new(['a'])
+ @r.delete('set')
+ @r.delete('set2')
+ end
+ #
+ it "should be able to do set difference and store the results in a key" do
+ @r.set_add "set", 'a'
+ @r.set_add "set", 'b'
+ @r.set_add "set2", 'b'
+ @r.set_add "set2", 'c'
+ @r.set_diff_store('newone', 'set', 'set2')
+ @r.set_members('newone').should == Set.new(['a'])
+ @r.delete('set')
+ @r.delete('set2')
+ end
#
it "should be able move elements from one set to another" do
@r.set_add 'set1', 'a'
@@ -350,6 +365,23 @@ describe "redis" do
@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.push_tail 'dogs', 1
+ @r['dog:2:name'] = 'lucy'
+ @r['dog:2:breed'] = 'poodle'
+ @r.push_tail 'dogs', 2
+ @r['dog:3:name'] = 'max'
+ @r['dog:3:breed'] = 'hound'
+ @r.push_tail 'dogs', 3
+ @r['dog:4:name'] = 'taj'
+ @r['dog:4:breed'] = 'terrier'
+ @r.push_tail '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" 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|
@@ -407,4 +439,7 @@ describe "redis" do
@r.pop_head('list').should == '42'
@r.delete('list')
end
+
+ it "should select db on connection"
+ it "should re-select db on reconnection"
end
diff --git a/client-libraries/ruby/spec/server_spec.rb b/client-libraries/ruby/spec/server_spec.rb
new file mode 100644
index 000000000..cb2beb569
--- /dev/null
+++ b/client-libraries/ruby/spec/server_spec.rb
@@ -0,0 +1,22 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe "Server" do
+ before(:each) do
+ @server = Server.new 'localhost', '6379'
+ end
+
+ it "should checkout active connections" do
+ threads = []
+ 10.times do
+ threads << Thread.new do
+ lambda {
+ socket = @server.socket
+ socket.close
+ socket.write("INFO\r\n")
+ socket.read(1)
+ }.should_not raise_error(Exception)
+ end
+ end
+ end
+
+end
diff --git a/client-libraries/ruby/tasks/redis.tasks.rb b/client-libraries/ruby/tasks/redis.tasks.rb
index 657d248db..580af2158 100644
--- a/client-libraries/ruby/tasks/redis.tasks.rb
+++ b/client-libraries/ruby/tasks/redis.tasks.rb
@@ -64,7 +64,7 @@ namespace :redis do
RedisRunner.attach
end
- desc 'Install the lastest redis from svn'
+ 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/"