From 69664139b5a57d08b2a17f624d5e9940761c9a3f Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 29 May 2009 12:28:37 +0200 Subject: [PATCH] ruby library client is not Redis-rb merged with RubyRedis "engine" by Brian McKinney --- client-libraries/ruby/lib/dist_redis.rb | 19 +- client-libraries/ruby/lib/pipeline.rb | 18 +- client-libraries/ruby/lib/redis.rb | 673 ++++++++-------------- client-libraries/ruby/lib/server.rb | 131 ----- client-libraries/ruby/redis-rb.gemspec | 2 +- client-libraries/ruby/spec/redis_spec.rb | 399 ++++++------- client-libraries/ruby/spec/server_spec.rb | 22 - client-libraries/ruby_2/rubyredis.rb | 238 -------- 8 files changed, 448 insertions(+), 1054 deletions(-) delete mode 100644 client-libraries/ruby/lib/server.rb delete mode 100644 client-libraries/ruby/spec/server_spec.rb delete mode 100644 client-libraries/ruby_2/rubyredis.rb diff --git a/client-libraries/ruby/lib/dist_redis.rb b/client-libraries/ruby/lib/dist_redis.rb index e79af472..830f8b6d 100644 --- a/client-libraries/ruby/lib/dist_redis.rb +++ b/client-libraries/ruby/lib/dist_redis.rb @@ -2,13 +2,20 @@ require 'redis' require 'hash_ring' class DistRedis attr_reader :ring - def initialize(*servers) - srvs = [] - servers.each do |s| - server, port = s.split(':') - srvs << Redis.new(:host => server, :port => port) + def initialize(opts={}) + hosts = [] + + db = opts[:db] || nil + timeout = opts[:timeout] || nil + + raise Error, "No hosts given" unless opts[:hosts] + + opts[:hosts].each do |h| + host, port = h.split(':') + hosts << Redis.new(:host => host, :port => port, :db => db, :timeout => timeout, :db => db) end - @ring = HashRing.new srvs + + @ring = HashRing.new hosts end def node_for_key(key) diff --git a/client-libraries/ruby/lib/pipeline.rb b/client-libraries/ruby/lib/pipeline.rb index f92b96db..acab5acc 100644 --- a/client-libraries/ruby/lib/pipeline.rb +++ b/client-libraries/ruby/lib/pipeline.rb @@ -8,19 +8,13 @@ class Redis @redis = redis @commands = [] end - - def execute_command(data) - @commands << data - write_and_read if @commands.size >= BUFFER_SIZE - end - - def finish - write_and_read + + def call_command(command) + @commands << command end - - def write_and_read - @redis.execute_command(@commands.join, true) - @redis.read_socket + + def execute + @redis.call_command(@commands) @commands.clear end diff --git a/client-libraries/ruby/lib/redis.rb b/client-libraries/ruby/lib/redis.rb index b10c42bf..28e304a6 100644 --- a/client-libraries/ruby/lib/redis.rb +++ b/client-libraries/ruby/lib/redis.rb @@ -1,464 +1,279 @@ require 'socket' -require 'set' -require File.join(File.dirname(__FILE__),'server') require File.join(File.dirname(__FILE__),'pipeline') -class RedisError < StandardError -end -class RedisRenameError < StandardError +begin + if (RUBY_VERSION >= '1.9') + require 'timeout' + RedisTimer = Timeout + else + require 'system_timer' + RedisTimer = SystemTimer + end +rescue LoadError + RedisTimer = nil end class Redis - ERR = "-".freeze - OK = 'OK'.freeze - PONG = 'PONG'.freeze - SINGLE = '+'.freeze - BULK = '$'.freeze - MULTI = '*'.freeze - INT = ':'.freeze - - 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} -> #{@db}" - end - - def port - @opts[:port] - end - - def host - @opts[:host] - end - - def quit - execute_command("QUIT\r\n", true) - end - - def ping - execute_command("PING\r\n") == PONG - end - - def select_db(index) - @db = index - execute_command("SELECT #{index}\r\n") - end - - def flush_db - execute_command("FLUSHDB\r\n") == OK - end - - def flush_all - 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 - execute_command("LASTSAVE\r\n").to_i - end - - def bgsave - execute_command("BGSAVE\r\n") == OK - end - - def info - info = {} - 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 - end - info - end - - def keys(glob) - execute_command("KEYS #{glob}\r\n").split(' ') - end - - def rename!(oldkey, newkey) - execute_command("RENAME #{oldkey} #{newkey}\r\n") - end - - def rename(oldkey, newkey) - case execute_command("RENAMENX #{oldkey} #{newkey}\r\n") - when -1 - raise RedisRenameError, "source key: #{oldkey} does not exist" - when 0 - raise RedisRenameError, "target key: #{oldkey} already exists" - when -3 - raise RedisRenameError, "source and destination keys are the same" - when 1 - true + BulkCommands = { + "set"=>true, "setnx"=>true, "rpush"=>true, "lpush"=>true, "lset"=>true, + "lrem"=>true, "sadd"=>true, "srem"=>true, "sismember"=>true, + "echo"=>true, "getset"=>true, "smove"=>true + } + + ConvertToBool = lambda do |r| + case r + when 0 then false + when 1 then true + else r + end end - end - - def key?(key) - execute_command("EXISTS #{key}\r\n") == 1 - end - - def delete(key) - execute_command("DEL #{key}\r\n") == 1 - end - - def [](key) - get(key) - end - def get(key) - execute_command("GET #{key}\r\n") - end - - def mget(*keys) - execute_command("MGET #{keys.join(' ')}\r\n") - end - - def incr(key, increment=nil) - if increment - execute_command("INCRBY #{key} #{increment}\r\n") - else - execute_command("INCR #{key}\r\n") - end - end - - def decr(key, decrement=nil) - if decrement - execute_command("DECRBY #{key} #{decrement}\r\n") - else - execute_command("DECR #{key}\r\n") - end - end - - def randkey - execute_command("RANDOMKEY\r\n") - end - - def list_length(key) - case i = execute_command("LLEN #{key}\r\n") - when -2 - raise RedisError, "key: #{key} does not hold a list value" - else - i + ReplyProcessor = { + "exists" => ConvertToBool, + "sismember"=> ConvertToBool, + "sadd"=> ConvertToBool, + "srem"=> ConvertToBool, + "smove"=> ConvertToBool, + "move"=> ConvertToBool, + "setnx"=> ConvertToBool, + "del"=> ConvertToBool, + "renamenx"=> ConvertToBool, + "expire"=> ConvertToBool, + "keys" => lambda{|r| r.split(" ")}, + "info" => lambda{|r| + info = {} + r.each_line {|kv| + k,v = kv.split(":",2).map{|x| x.chomp} + info[k.to_sym] = v + } + info + } + } + + Aliases = { + "flush_db" => "flushdb", + "flush_all" => "flushall", + "last_save" => "lastsave", + "key?" => "exists", + "delete" => "del", + "randkey" => "randomkey", + "list_length" => "llen", + "push_tail" => "rpush", + "push_head" => "lpush", + "pop_tail" => "rpop", + "pop_head" => "lpop", + "list_set" => "lset", + "list_range" => "lrange", + "list_trim" => "ltrim", + "list_index" => "lindex", + "list_rm" => "lrem", + "set_add" => "sadd", + "set_delete" => "srem", + "set_count" => "scard", + "set_member?" => "sismember", + "set_members" => "smembers", + "set_intersect" => "sinter", + "set_intersect_store" => "sinterstore", + "set_inter_store" => "sinterstore", + "set_union" => "sunion", + "set_union_store" => "sunionstore", + "set_diff" => "sdiff", + "set_diff_store" => "sdiffstore", + "set_move" => "smove", + "set_unless_exists" => "setnx", + "rename_unless_exists" => "renamenx", + "type?" => "type" + } + + def initialize(opts={}) + @host = opts[:host] || '127.0.0.1' + @port = opts[:port] || 6379 + @db = opts[:db] || 0 + @timeout = opts[:timeout] || 5 + $debug = opts[:debug] || false + connect_to_server end - end - - def type?(key) - execute_command("TYPE #{key}\r\n") - end - - def push_tail(key, val) - execute_command("RPUSH #{key} #{value_to_wire(val)}\r\n") - end - - def push_head(key, val) - execute_command("LPUSH #{key} #{value_to_wire(val)}\r\n") - end - - def pop_head(key) - execute_command("LPOP #{key}\r\n") - end - - def pop_tail(key) - execute_command("RPOP #{key}\r\n") - end - - def list_set(key, index, val) - execute_command("LSET #{key} #{index} #{value_to_wire(val)}\r\n") == OK - end - - def list_range(key, start, ending) - execute_command("LRANGE #{key} #{start} #{ending}\r\n") - end - def list_trim(key, start, ending) - execute_command("LTRIM #{key} #{start} #{ending}\r\n") - end - - def list_index(key, index) - execute_command("LINDEX #{key} #{index}\r\n") - end - - 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 - raise RedisError, "key: #{key} does not hold a list value" - else - num + def to_s + "Redis Client connected to #{@host}:#{@port} against DB #{@db}" end - end - def set_add(key, member) - case execute_command("SADD #{key} #{value_to_wire(member)}\r\n") - when 1 - true - when 0 - false - when -2 - raise RedisError, "key: #{key} contains a non set value" + def connect_to_server + @sock = connect_to(@host,@port,@timeout == 0 ? nil : @timeout) + call_command(["select",@db]) if @db != 0 end - end - def set_delete(key, member) - case execute_command("SREM #{key} #{value_to_wire(member)}\r\n") - when 1 - true - when 0 - false - when -2 - raise RedisError, "key: #{key} contains a non set value" + def connect_to(host, port, timeout=nil) + # We support connect() timeout only if system_timer is availabe + # or if we are running against Ruby >= 1.9 + # Timeout reading from the socket instead will be supported anyway. + if @timeout != 0 and RedisTimer + begin + sock = TCPSocket.new(host, port) + rescue Timeout::Error + @sock = nil + raise Timeout::Error, "Timeout connecting to the server" + end + else + sock = TCPSocket.new(host, port) + end + sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1 + + # If the timeout is set we set the low level socket options in order + # to make sure a blocking read will return after the specified number + # of seconds. This hack is from memcached ruby client. + if timeout + secs = Integer(timeout) + usecs = Integer((timeout - secs) * 1_000_000) + optval = [secs, usecs].pack("l_2") + sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval + sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval + end + sock end - end - def set_count(key) - case i = execute_command("SCARD #{key}\r\n") - when -2 - raise RedisError, "key: #{key} contains a non set value" - else - i + def method_missing(*argv) + call_command(argv) end - end - def set_member?(key, member) - case execute_command("SISMEMBER #{key} #{value_to_wire(member)}\r\n") - when 1 - true - when 0 - false - when -2 - raise RedisError, "key: #{key} contains a non set value" + def call_command(argv) + puts argv.inspect if $debug + # this wrapper to raw_call_command handle reconnection on socket + # error. We try to reconnect just one time, otherwise let the error + # araise. + connect_to_server if !@sock + begin + raw_call_command(argv) + rescue Errno::ECONNRESET + @sock.close + connect_to_server + raw_call_command(argv) + end end - end - - def set_members(key) - Set.new(execute_command("SMEMBERS #{key}\r\n")) - end - - def set_intersect(*keys) - Set.new(execute_command("SINTER #{keys.join(' ')}\r\n")) - end - def set_inter_store(destkey, *keys) - execute_command("SINTERSTORE #{destkey} #{keys.join(' ')}\r\n") - end - - def set_union(*keys) - Set.new(execute_command("SUNION #{keys.join(' ')}\r\n")) - end - - def set_union_store(destkey, *keys) - execute_command("SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n") - end - - def set_diff(*keys) - Set.new(execute_command("SDIFF #{keys.join(' ')}\r\n")) - end - - def set_diff_store(destkey, *keys) - execute_command("SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n") - end + def raw_call_command(argvp) + pipeline = argvp[0].is_a?(Array) + + unless pipeline + argvv = [argvp] + else + argvv = argvp + end + + command = '' + + argvv.each do |argv| + bulk = nil + argv[0] = argv[0].to_s.downcase + argv[0] = Aliases[argv[0]] if Aliases[argv[0]] + if BulkCommands[argv[0]] and argv.length > 1 + bulk = argv[-1].to_s + argv[-1] = bulk.length + end + command << argv.join(' ') + "\r\n" + command << bulk + "\r\n" if bulk + end + + @sock.write(command) + + results = argvv.map do |argv| + processor = ReplyProcessor[argv[0]] + processor ? processor.call(read_reply) : read_reply + end + + return pipeline ? results : results[0] + end - def set_move(srckey, destkey, member) - execute_command("SMOVE #{srckey} #{destkey} #{value_to_wire(member)}\r\n") == 1 - end + def select(*args) + raise "SELECT not allowed, use the :db option when creating the object" + end - def sort(key, opts={}) - cmd = "SORT #{key}" - cmd << " BY #{opts[:by]}" if opts[:by] - 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" - 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 [](key) + get(key) + end - def dbsize - execute_command("DBSIZE\r\n") - end + def []=(key,value) + set(key,value) + end - def expire(key, expiry=nil) - execute_command("EXPIRE #{key} #{expiry}\r\n") == 1 - end + def set(key, value, expiry=nil) + call_command([:set, key, value]) + expire(key, expiry) unless expiry.nil? + 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 + def sort(key, opts={}) + cmd = [] + cmd << "SORT #{key}" + cmd << "BY #{opts[:by]}" if opts[:by] + cmd << "GET #{[opts[:get]].flatten * ' GET '}" if opts[:get] + cmd << "#{opts[:order]}" if opts[:order] + cmd << "LIMIT #{opts[:limit].join(' ')}" if opts[:limit] + call_command(cmd) 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 - res = read_proto - puts "mb res is #{res.inspect}" if $debug - list = [] - Integer(res).times do - vf = get_response - puts "curren vf is #{vf.inspect}" if $debug - list << vf - puts "current list is #{list.inspect}" if $debug + + def incr(key,increment=nil) + call_command(increment ? ["incrby",key,increment] : ["incr",key]) end - list - end - - def get_reply - begin - r = read(1) - raise RedisError if (r == "\r" || r == "\n") - rescue RedisError - retry + + def decr(key,decrement=nil) + call_command(decrement ? ["decrby",key,decrement] : ["decr",key]) end - r - end - - def status_code_reply - begin - res = read_proto - if res.index('-') == 0 - raise RedisError, res - else - true - end - rescue RedisError - raise RedisError + + # Ruby defines a now deprecated type method so we need to override it here + # since it will never hit method_missing + def type(key) + call_command(['type', key]) end - 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 - rtype = get_reply - puts "reply_type is #{rtype.inspect}" if $debug - case rtype - when SINGLE - single_line - when BULK - bulk_reply - when MULTI - multi_bulk - when INT - integer_reply - when ERR - raise RedisError, single_line - else - raise RedisError, "Unknown response.." - end - end - - def integer_reply - Integer(read_proto) - end - - def single_line - buff = "" - while buff[-2..-1] != "\r\n" - buff << read(1) + def quit + call_command(['quit']) + rescue Errno::ECONNRESET end - puts "single_line value is #{buff[0..-3].inspect}" if $debug - buff[0..-3] - end - - def read_socket - begin - socket = @server.socket - while res = socket.read(8096) - break if res.size != 8096 - end - #Timeout or server down - rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED => e - server.close - puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug - retry - rescue Timeout::Error => e - #BTM - Ignore this error so we don't go into an endless loop - puts "Client (#{server.inspect}) Timeout\n" if $debug - #Server down - rescue NoMethodError => e - puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug - raise Errno::ECONNREFUSED - #exit + + def pipelined(&block) + pipeline = Pipeline.new self + yield pipeline + pipeline.execute end - end - - def read_proto - 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 + def read_reply + # We read the first byte using read() mainly because gets() is + # immune to raw socket timeouts. + begin + rtype = @sock.read(1) + rescue Errno::EAGAIN + # We want to make sure it reconnects on the next command after the + # timeout. Otherwise the server may reply in the meantime leaving + # the protocol in a desync status. + @sock = nil + raise Errno::EAGAIN, "Timeout reading from the socket" + end + + raise Errno::ECONNRESET,"Connection lost" if !rtype + line = @sock.gets + case rtype + when "-" + raise "-"+line.strip + when "+" + line.strip + when ":" + line.to_i + when "$" + bulklen = line.to_i + return nil if bulklen == -1 + data = @sock.read(bulklen) + @sock.read(2) # CRLF + data + when "*" + objects = line.to_i + return nil if bulklen == -1 + res = [] + objects.times { + res << read_reply + } + res + else + raise "Protocol error, got '#{rtype}' as initial reply byte" + end end - "#{value_size}\r\n#{value_str}" - end - end diff --git a/client-libraries/ruby/lib/server.rb b/client-libraries/ruby/lib/server.rb deleted file mode 100644 index 4fb54937..00000000 --- a/client-libraries/ruby/lib/server.rb +++ /dev/null @@ -1,131 +0,0 @@ -begin - # Timeout code is courtesy of Ruby memcache-client - # http://github.com/mperham/memcache-client/tree - # Try to use the SystemTimer gem instead of Ruby's timeout library - # when running on something that looks like Ruby 1.8.x. See: - # http://ph7spot.com/articles/system_timer - # We don't want to bother trying to load SystemTimer on jruby and - # ruby 1.9+. - if defined?(JRUBY_VERSION) || (RUBY_VERSION >= '1.9') - require 'timeout' - RedisTimer = Timeout - else - require 'system_timer' - RedisTimer = SystemTimer - end -rescue LoadError => e - puts "[redis-rb] Could not load SystemTimer gem, falling back to Ruby's slower/unsafe timeout library: #{e.message}" - require 'timeout' - RedisTimer = Timeout -end - -## -# This class represents a redis server instance. - -class Server - - ## - # The host the redis server is running on. - - attr_reader :host - - ## - # The port the redis server is listening on. - - attr_reader :port - - ## - # A text status string describing the state of the server. - - attr_reader :status - - ## - # Create a new Redis::Server object for the redis instance - # listening on the given host and port. - - def initialize(host, port = DEFAULT_PORT, timeout = 10) - raise ArgumentError, "No host specified" if host.nil? or host.empty? - raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero? - - @host = host - @port = port.to_i - - @sock = nil - @status = 'NOT CONNECTED' - @timeout = timeout - end - - ## - # Return a string representation of the server object. - def inspect - "" % [@host, @port, @status] - end - - ## - # Try to connect to the redis server targeted by this object. - # Returns the connected socket object on success or nil on failure. - - def socket - 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 - @status = 'CONNECTED' - rescue Errno::EPIPE, Errno::ECONNREFUSED => e - 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 - end - @sock - end - - def connect_to(host, port, timeout=nil) - socket = TCPSocket.new(host, port) - socket.set_encoding(Encoding::BINARY) if socket.respond_to?(:set_encoding) - if timeout - socket.instance_eval <<-EOR - alias :blocking_readline :readline - def readline(*args) - RedisTimer.timeout(#{timeout}) do - self.blocking_readline(*args) - end - end - alias :blocking_read :read - def read(*args) - RedisTimer.timeout(#{timeout}) do - self.blocking_read(*args) - end - end - alias :blocking_write :write - def write(*args) - RedisTimer.timeout(#{timeout}) do - self.blocking_write(*args) - end - end - EOR - end - socket - end - - # Close the connection to the redis server targeted by this - # object. - - def close - @sock.close if !@sock.nil? && !@sock.closed? - @sock = nil - @status = "NOT CONNECTED" - 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 e8d8b6eb..2cd25211 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.5" + s.version = "0.0.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 de302f34..d4dde964 100644 --- a/client-libraries/ruby/spec/redis_spec.rb +++ b/client-libraries/ruby/spec/redis_spec.rb @@ -22,7 +22,7 @@ describe "redis" do end after(:each) do - @r.keys('*').each {|k| @r.delete k} + @r.keys('*').each {|k| @r.del k} end after(:all) do @@ -30,7 +30,7 @@ describe "redis" do end it 'should be able to PING' do - @r.ping.should == true + @r.ping.should == 'PONG' end it "should be able to GET a key" do @@ -62,22 +62,22 @@ describe "redis" do @r['foo'].should == nil end - it "should be able to SETNX(set_unless_exists)" do + it "should be able to SETNX" do @r['foo'] = 'nik' @r['foo'].should == 'nik' - @r.set_unless_exists 'foo', 'bar' + @r.setnx 'foo', 'bar' @r['foo'].should == 'nik' end # - it "should be able to INCR(increment) a key" do - @r.delete('counter') + it "should be able to INCR a key" do + @r.del('counter') @r.incr('counter').should == 1 @r.incr('counter').should == 2 @r.incr('counter').should == 3 end # - it "should be able to DECR(decrement) a key" do - @r.delete('counter') + it "should be able to DECR a key" do + @r.del('counter') @r.incr('counter').should == 1 @r.incr('counter').should == 2 @r.incr('counter').should == 3 @@ -85,24 +85,24 @@ describe "redis" do @r.decr('counter', 2).should == 0 end # - it "should be able to RANDKEY(return a random key)" do + it "should be able to RANDKEY" do @r.randkey.should_not be_nil end # it "should be able to RENAME a key" do - @r.delete 'foo' - @r.delete 'bar' + @r.del 'foo' + @r.del'bar' @r['foo'] = 'hi' - @r.rename! 'foo', 'bar' + @r.rename 'foo', 'bar' @r['bar'].should == 'hi' end # - it "should be able to RENAMENX(rename unless the new key already exists) a key" do - @r.delete 'foo' - @r.delete 'bar' + it "should be able to RENAMENX a key" do + @r.del 'foo' + @r.del 'bar' @r['foo'] = 'hi' @r['bar'] = 'ohai' - lambda {@r.rename 'foo', 'bar'}.should raise_error(RedisRenameError) + @r.renamenx 'foo', 'bar' @r['bar'].should == 'ohai' end # @@ -117,251 +117,224 @@ describe "redis" do # it "should be able to EXPIRE a key" do @r['foo'] = 'bar' - @r.expire('foo', 1) + @r.expire 'foo', 1 @r['foo'].should == "bar" sleep 2 @r['foo'].should == nil end # - it "should be able to EXISTS(check if key exists)" do + it "should be able to EXISTS" do @r['foo'] = 'nik' - @r.key?('foo').should be_true - @r.delete 'foo' - @r.key?('foo').should be_false + @r.exists('foo').should be_true + @r.del 'foo' + @r.exists('foo').should be_false end # - it "should be able to KEYS(glob for keys)" do - @r.keys("f*").each do |key| - @r.delete key - end + it "should be able to KEYS" do + @r.keys("f*").each { |key| @r.del key } @r['f'] = 'nik' @r['fo'] = 'nak' @r['foo'] = 'qux' @r.keys("f*").sort.should == ['f','fo', 'foo'].sort end - # + #BTM - TODO it "should be able to check the TYPE of a key" do @r['foo'] = 'nik' - @r.type?('foo').should == "string" - @r.delete 'foo' - @r.type?('foo').should == "none" + @r.type('foo').should == "string" + @r.del 'foo' + @r.type('foo').should == "none" end # - it "should be able to push to the head of a list" do - @r.push_head "list", 'hello' - @r.push_head "list", 42 - @r.type?('list').should == "list" - @r.list_length('list').should == 2 - @r.pop_head('list').should == '42' - @r.delete('list') + it "should be able to push to the head of a list (LPUSH)" do + @r.lpush "list", 'hello' + @r.lpush "list", 42 + @r.type('list').should == "list" + @r.llen('list').should == 2 + @r.lpop('list').should == '42' end # - it "should be able to push to the tail of a list" do - @r.push_tail "list", 'hello' - @r.type?('list').should == "list" - @r.list_length('list').should == 1 - @r.delete('list') + it "should be able to push to the tail of a list (RPUSH)" do + @r.rpush "list", 'hello' + @r.type('list').should == "list" + @r.llen('list').should == 1 end # - it "should be able to pop the tail of a list" do - @r.push_tail "list", 'hello' - @r.push_tail "list", 'goodbye' - @r.type?('list').should == "list" - @r.list_length('list').should == 2 - @r.pop_tail('list').should == 'goodbye' - @r.delete('list') + it "should be able to pop the tail of a list (RPOP)" do + @r.rpush "list", 'hello' + @r.rpush"list", 'goodbye' + @r.type('list').should == "list" + @r.llen('list').should == 2 + @r.rpop('list').should == 'goodbye' end # - it "should be able to pop the head of a list" do - @r.push_tail "list", 'hello' - @r.push_tail "list", 'goodbye' - @r.type?('list').should == "list" - @r.list_length('list').should == 2 - @r.pop_head('list').should == 'hello' - @r.delete('list') + it "should be able to pop the head of a list (LPOP)" do + @r.rpush "list", 'hello' + @r.rpush "list", 'goodbye' + @r.type('list').should == "list" + @r.llen('list').should == 2 + @r.lpop('list').should == 'hello' end # - it "should be able to get the length of a list" do - @r.push_tail "list", 'hello' - @r.push_tail "list", 'goodbye' - @r.type?('list').should == "list" - @r.list_length('list').should == 2 - @r.delete('list') + it "should be able to get the length of a list (LLEN)" do + @r.rpush "list", 'hello' + @r.rpush "list", 'goodbye' + @r.type('list').should == "list" + @r.llen('list').should == 2 end # - it "should be able to get a range of values from a list" do - @r.push_tail "list", 'hello' - @r.push_tail "list", 'goodbye' - @r.push_tail "list", '1' - @r.push_tail "list", '2' - @r.push_tail "list", '3' - @r.type?('list').should == "list" - @r.list_length('list').should == 5 - @r.list_range('list', 2, -1).should == ['1', '2', '3'] - @r.delete('list') + it "should be able to get a range of values from a list (LRANGE)" do + @r.rpush "list", 'hello' + @r.rpush "list", 'goodbye' + @r.rpush "list", '1' + @r.rpush "list", '2' + @r.rpush "list", '3' + @r.type('list').should == "list" + @r.llen('list').should == 5 + @r.lrange('list', 2, -1).should == ['1', '2', '3'] end # - it "should be able to trim a list" do - @r.push_tail "list", 'hello' - @r.push_tail "list", 'goodbye' - @r.push_tail "list", '1' - @r.push_tail "list", '2' - @r.push_tail "list", '3' - @r.type?('list').should == "list" - @r.list_length('list').should == 5 - @r.list_trim 'list', 0, 1 - @r.list_length('list').should == 2 - @r.list_range('list', 0, -1).should == ['hello', 'goodbye'] - @r.delete('list') + it "should be able to trim a list (LTRIM)" do + @r.rpush "list", 'hello' + @r.rpush "list", 'goodbye' + @r.rpush "list", '1' + @r.rpush "list", '2' + @r.rpush "list", '3' + @r.type('list').should == "list" + @r.llen('list').should == 5 + @r.ltrim 'list', 0, 1 + @r.llen('list').should == 2 + @r.lrange('list', 0, -1).should == ['hello', 'goodbye'] end # - it "should be able to get a value by indexing into a list" do - @r.push_tail "list", 'hello' - @r.push_tail "list", 'goodbye' - @r.type?('list').should == "list" - @r.list_length('list').should == 2 - @r.list_index('list', 1).should == 'goodbye' - @r.delete('list') + it "should be able to get a value by indexing into a list (LINDEX)" do + @r.rpush "list", 'hello' + @r.rpush "list", 'goodbye' + @r.type('list').should == "list" + @r.llen('list').should == 2 + @r.lindex('list', 1).should == 'goodbye' end # - it "should be able to set a value by indexing into a list" do - @r.push_tail "list", 'hello' - @r.push_tail "list", 'hello' - @r.type?('list').should == "list" - @r.list_length('list').should == 2 - @r.list_set('list', 1, 'goodbye').should be_true - @r.list_index('list', 1).should == 'goodbye' - @r.delete('list') + it "should be able to set a value by indexing into a list (LSET)" do + @r.rpush "list", 'hello' + @r.rpush "list", 'hello' + @r.type('list').should == "list" + @r.llen('list').should == 2 + @r.lset('list', 1, 'goodbye').should == 'OK' + @r.lindex('list', 1).should == 'goodbye' end # - it "should be able to remove values from a list LREM" do - @r.push_tail "list", 'hello' - @r.push_tail "list", 'goodbye' - @r.type?('list').should == "list" - @r.list_length('list').should == 2 - @r.list_rm('list', 1, 'hello').should == 1 - @r.list_range('list', 0, -1).should == ['goodbye'] - @r.delete('list') + it "should be able to remove values from a list (LREM)" do + @r.rpush "list", 'hello' + @r.rpush "list", 'goodbye' + @r.type('list').should == "list" + @r.llen('list').should == 2 + @r.lrem('list', 1, 'hello').should == 1 + @r.lrange('list', 0, -1).should == ['goodbye'] end # - it "should be able add members to a set" do - @r.set_add "set", 'key1' - @r.set_add "set", 'key2' - @r.type?('set').should == "set" - @r.set_count('set').should == 2 - @r.set_members('set').sort.should == ['key1', 'key2'].sort - @r.delete('set') + it "should be able add members to a set (SADD)" do + @r.sadd "set", 'key1' + @r.sadd "set", 'key2' + @r.type('set').should == "set" + @r.scard('set').should == 2 + @r.smembers('set').sort.should == ['key1', 'key2'].sort end # - it "should be able delete members to a set" do - @r.set_add "set", 'key1' - @r.set_add "set", 'key2' - @r.type?('set').should == "set" - @r.set_count('set').should == 2 - @r.set_members('set').should == Set.new(['key1', 'key2']) - @r.set_delete('set', 'key1') - @r.set_count('set').should == 1 - @r.set_members('set').should == Set.new(['key2']) - @r.delete('set') + it "should be able delete members to a set (SREM)" do + @r.sadd "set", 'key1' + @r.sadd "set", 'key2' + @r.type('set').should == "set" + @r.scard('set').should == 2 + @r.smembers('set').sort.should == ['key1', 'key2'].sort + @r.srem('set', 'key1') + @r.scard('set').should == 1 + @r.smembers('set').should == ['key2'] end # - it "should be able count the members of a set" do - @r.set_add "set", 'key1' - @r.set_add "set", 'key2' - @r.type?('set').should == "set" - @r.set_count('set').should == 2 - @r.delete('set') + it "should be able count the members of a set (SCARD)" do + @r.sadd "set", 'key1' + @r.sadd "set", 'key2' + @r.type('set').should == "set" + @r.scard('set').should == 2 end # - it "should be able test for set membership" do - @r.set_add "set", 'key1' - @r.set_add "set", 'key2' - @r.type?('set').should == "set" - @r.set_count('set').should == 2 - @r.set_member?('set', 'key1').should be_true - @r.set_member?('set', 'key2').should be_true - @r.set_member?('set', 'notthere').should be_false - @r.delete('set') + it "should be able test for set membership (SISMEMBER)" do + @r.sadd "set", 'key1' + @r.sadd "set", 'key2' + @r.type('set').should == "set" + @r.scard('set').should == 2 + @r.sismember('set', 'key1').should be_true + @r.sismember('set', 'key2').should be_true + @r.sismember('set', 'notthere').should be_false end # - it "should be able to do set intersection" do - @r.set_add "set", 'key1' - @r.set_add "set", 'key2' - @r.set_add "set2", 'key2' - @r.set_intersect('set', 'set2').should == Set.new(['key2']) - @r.delete('set') + it "should be able to do set intersection (SINTER)" do + @r.sadd "set", 'key1' + @r.sadd "set", 'key2' + @r.sadd "set2", 'key2' + @r.sinter('set', 'set2').should == ['key2'] end # - it "should be able to do set intersection and store the results in a key" do - @r.set_add "set", 'key1' - @r.set_add "set", 'key2' - @r.set_add "set2", 'key2' - @r.set_inter_store('newone', 'set', 'set2').should == 'OK' - @r.set_members('newone').should == Set.new(['key2']) - @r.delete('set') - @r.delete('set2') + it "should be able to do set intersection and store the results in a key (SINTERSTORE)" do + @r.sadd "set", 'key1' + @r.sadd "set", 'key2' + @r.sadd "set2", 'key2' + @r.sinterstore('newone', 'set', 'set2').should == 1 + @r.smembers('newone').should == ['key2'] end # - it "should be able to do set union" do - @r.set_add "set", 'key1' - @r.set_add "set", 'key2' - @r.set_add "set2", 'key2' - @r.set_add "set2", 'key3' - @r.set_union('set', 'set2').should == Set.new(['key1','key2','key3']) - @r.delete('set') - @r.delete('set2') + it "should be able to do set union (SUNION)" do + @r.sadd "set", 'key1' + @r.sadd "set", 'key2' + @r.sadd "set2", 'key2' + @r.sadd "set2", 'key3' + @r.sunion('set', 'set2').sort.should == ['key1','key2','key3'].sort end # - it "should be able to do set union and store the results in a key" do - @r.set_add "set", 'key1' - @r.set_add "set", 'key2' - @r.set_add "set2", 'key2' - @r.set_add "set2", 'key3' - @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') + it "should be able to do set union and store the results in a key (SUNIONSTORE)" do + @r.sadd "set", 'key1' + @r.sadd "set", 'key2' + @r.sadd "set2", 'key2' + @r.sadd "set2", 'key3' + @r.sunionstore('newone', 'set', 'set2').should == 3 + @r.smembers('newone').sort.should == ['key1','key2','key3'].sort end # - it "should be able to do set difference" 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') + it "should be able to do set difference (SDIFF)" do + @r.sadd "set", 'a' + @r.sadd "set", 'b' + @r.sadd "set2", 'b' + @r.sadd "set2", 'c' + @r.sdiff('set', 'set2').should == ['a'] end # - it "should be able to do set difference and store the results in a key" 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') + it "should be able to do set difference and store the results in a key (SDIFFSTORE)" do + @r.sadd "set", 'a' + @r.sadd "set", 'b' + @r.sadd "set2", 'b' + @r.sadd "set2", 'c' + @r.sdiffstore('newone', 'set', 'set2') + @r.smembers('newone').should == ['a'] end # - it "should be able move elements from one set to another" do - @r.set_add 'set1', 'a' - @r.set_add 'set1', 'b' - @r.set_add 'set2', 'x' - @r.set_move('set1', 'set2', 'a').should == true - @r.set_member?('set2', 'a').should == true + it "should be able move elements from one set to another (SMOVE)" do + @r.sadd 'set1', 'a' + @r.sadd 'set1', 'b' + @r.sadd 'set2', 'x' + @r.smove('set1', 'set2', 'a').should be_true + @r.sismember('set2', 'a').should be_true @r.delete('set1') end # it "should be able to do crazy SORT queries" do @r['dog_1'] = 'louie' - @r.push_tail 'dogs', 1 + @r.rpush 'dogs', 1 @r['dog_2'] = 'lucy' - @r.push_tail 'dogs', 2 + @r.rpush 'dogs', 2 @r['dog_3'] = 'max' - @r.push_tail 'dogs', 3 + @r.rpush 'dogs', 3 @r['dog_4'] = 'taj' - @r.push_tail 'dogs', 4 + @r.rpush 'dogs', 4 @r.sort('dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie'] @r.sort('dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj'] end @@ -369,16 +342,16 @@ describe "redis" do 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.rpush 'dogs', 1 @r['dog:2:name'] = 'lucy' @r['dog:2:breed'] = 'poodle' - @r.push_tail 'dogs', 2 + @r.rpush 'dogs', 2 @r['dog:3:name'] = 'max' @r['dog:3:breed'] = 'hound' - @r.push_tail 'dogs', 3 + @r.rpush 'dogs', 3 @r['dog:4:name'] = 'taj' @r['dog:4:breed'] = 'terrier' - @r.push_tail 'dogs', 4 + @r.rpush 'dogs', 4 @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1]).should == ['louie', 'mutt'] @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1], :order => 'desc alpha').should == ['taj', 'terrier'] end @@ -392,13 +365,13 @@ describe "redis" do it "should be able to flush the database" do @r['key1'] = 'keyone' @r['key2'] = 'keytwo' - @r.keys('*').sort.should == ['foo', 'key1', 'key2'] #foo from before - @r.flush_db + @r.keys('*').sort.should == ['foo', 'key1', 'key2'].sort #foo from before + @r.flushdb @r.keys('*').should == [] end # - it "should be able to provide the last save time" do - savetime = @r.last_save + it "should be able to provide the last save time (LASTSAVE)" do + savetime = @r.lastsave Time.at(savetime).class.should == Time Time.at(savetime).should <= Time.now end @@ -411,13 +384,12 @@ describe "redis" do end it "should bgsave" do - lambda {@r.bgsave}.should_not raise_error(RedisError) + @r.bgsave.should == 'OK' end it "should handle multiple servers" do require 'dist_redis' - @r = DistRedis.new('localhost:6379', '127.0.0.1:6379') - @r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data + @r = DistRedis.new(:hosts=> ['localhost:6379', '127.0.0.1:6379'], :db => 15) 100.times do |idx| @r[idx] = "foo#{idx}" @@ -430,16 +402,13 @@ describe "redis" do it "should be able to pipeline writes" do @r.pipelined do |pipeline| - pipeline.push_head "list", "hello" - pipeline.push_head "list", 42 + pipeline.lpush 'list', "hello" + pipeline.lpush 'list', 42 end - @r.type?('list').should == "list" - @r.list_length('list').should == 2 - @r.pop_head('list').should == '42' - @r.delete('list') + @r.type('list').should == "list" + @r.llen('list').should == 2 + @r.lpop('list').should == '42' 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 deleted file mode 100644 index cb2beb56..00000000 --- a/client-libraries/ruby/spec/server_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -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_2/rubyredis.rb b/client-libraries/ruby_2/rubyredis.rb deleted file mode 100644 index ca73d817..00000000 --- a/client-libraries/ruby_2/rubyredis.rb +++ /dev/null @@ -1,238 +0,0 @@ -# RubyRedis is an alternative implementatin of Ruby client library written -# by Salvatore Sanfilippo. -# -# The aim of this library is to create an alternative client library that is -# much simpler and does not implement every command explicitly but uses -# method_missing instead. - -require 'socket' -require 'set' - -begin - if (RUBY_VERSION >= '1.9') - require 'timeout' - RedisTimer = Timeout - else - require 'system_timer' - RedisTimer = SystemTimer - end -rescue LoadError - RedisTimer = nil -end - -class RedisClient - BulkCommands = { - "set"=>true, "setnx"=>true, "rpush"=>true, "lpush"=>true, "lset"=>true, - "lrem"=>true, "sadd"=>true, "srem"=>true, "sismember"=>true, - "echo"=>true, "getset"=>true, "smove"=>true - } - - ConvertToBool = lambda{|r| r == 0 ? false : r} - - ReplyProcessor = { - "exists" => ConvertToBool, - "sismember"=> ConvertToBool, - "sadd"=> ConvertToBool, - "srem"=> ConvertToBool, - "smove"=> ConvertToBool, - "move"=> ConvertToBool, - "setnx"=> ConvertToBool, - "del"=> ConvertToBool, - "renamenx"=> ConvertToBool, - "expire"=> ConvertToBool, - "keys" => lambda{|r| r.split(" ")}, - "info" => lambda{|r| - info = {} - r.each_line {|kv| - k,v = kv.split(":",2).map{|x| x.chomp} - info[k.to_sym] = v - } - info - } - } - - Aliases = { - "flush_db" => "flushdb", - "flush_all" => "flushall", - "last_save" => "lastsave", - "key?" => "exists", - "delete" => "del", - "randkey" => "randomkey", - "list_length" => "llen", - "push_tail" => "rpush", - "push_head" => "lpush", - "pop_tail" => "rpop", - "pop_head" => "lpop", - "list_set" => "lset", - "list_range" => "lrange", - "list_trim" => "ltrim", - "list_index" => "lindex", - "list_rm" => "lrem", - "set_add" => "sadd", - "set_delete" => "srem", - "set_count" => "scard", - "set_member?" => "sismember", - "set_members" => "smembers", - "set_intersect" => "sinter", - "set_intersect_store" => "sinterstore", - "set_inter_store" => "sinterstore", - "set_union" => "sunion", - "set_union_store" => "sunionstore", - "set_diff" => "sdiff", - "set_diff_store" => "sdiffstore", - "set_move" => "smove", - "set_unless_exists" => "setnx", - "rename_unless_exists" => "renamenx" - } - - def initialize(opts={}) - @host = opts[:host] || '127.0.0.1' - @port = opts[:port] || 6379 - @db = opts[:db] || 0 - @timeout = opts[:timeout] || 0 - connect_to_server - end - - def to_s - "Redis Client connected to #{@host}:#{@port} against DB #{@db}" - end - - def connect_to_server - @sock = connect_to(@host,@port,@timeout == 0 ? nil : @timeout) - call_command(["select",@db]) if @db != 0 - end - - def connect_to(host, port, timeout=nil) - # We support connect() timeout only if system_timer is availabe - # or if we are running against Ruby >= 1.9 - # Timeout reading from the socket instead will be supported anyway. - if @timeout != 0 and RedisTimer - begin - sock = TCPSocket.new(host, port, 0) - rescue Timeout::Error - @sock = nil - raise Timeout::Error, "Timeout connecting to the server" - end - else - sock = TCPSocket.new(host, port, 0) - end - sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1 - - # If the timeout is set we set the low level socket options in order - # to make sure a blocking read will return after the specified number - # of seconds. This hack is from memcached ruby client. - if timeout - secs = Integer(timeout) - usecs = Integer((timeout - secs) * 1_000_000) - optval = [secs, usecs].pack("l_2") - sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval - sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval - end - sock - end - - def method_missing(*argv) - call_command(argv) - end - - def call_command(argv) - # this wrapper to raw_call_command handle reconnection on socket - # error. We try to reconnect just one time, otherwise let the error - # araise. - connect_to_server if !@sock - begin - raw_call_command(argv) - rescue Errno::ECONNRESET - @sock.close - connect_to_server - raw_call_command(argv) - end - end - - def raw_call_command(argv) - bulk = nil - argv[0] = argv[0].to_s.downcase - argv[0] = Aliases[argv[0]] if Aliases[argv[0]] - if BulkCommands[argv[0]] and argv.length > 1 - bulk = argv[-1].to_s - argv[-1] = bulk.length - end - @sock.write(argv.join(" ")+"\r\n") - @sock.write(bulk+"\r\n") if bulk - - # Post process the reply if needed - processor = ReplyProcessor[argv[0]] - processor ? processor.call(read_reply) : read_reply - end - - def select(*args) - raise "SELECT not allowed, use the :db option when creating the object" - end - - def [](key) - get(key) - end - - def []=(key,value) - set(key,value) - end - - def sort(key, opts={}) - cmd = [] - cmd << "SORT #{key}" - cmd << "BY #{opts[:by]}" if opts[:by] - cmd << "GET #{[opts[:get]].flatten * ' GET '}" if opts[:get] - cmd << "#{opts[:order]}" if opts[:order] - cmd << "LIMIT #{opts[:limit].join(' ')}" if opts[:limit] - call_command(cmd) - end - - def incr(key,increment=nil) - call_command(increment ? ["incrby",key,increment] : ["incr",key]) - end - - def decr(key,decrement=nil) - call_command(decrement ? ["decrby",key,decrement] : ["decr",key]) - end - - def read_reply - # We read the first byte using read() mainly because gets() is - # immune to raw socket timeouts. - begin - rtype = @sock.read(1) - rescue Errno::EAGAIN - # We want to make sure it reconnects on the next command after the - # timeout. Otherwise the server may reply in the meantime leaving - # the protocol in a desync status. - @sock = nil - raise Errno::EAGAIN, "Timeout reading from the socket" - end - - raise Errno::ECONNRESET,"Connection lost" if !rtype - line = @sock.gets - case rtype - when "-" - raise "-"+line.strip - when "+" - line.strip - when ":" - line.to_i - when "$" - bulklen = line.to_i - return nil if bulklen == -1 - data = @sock.read(bulklen) - @sock.read(2) # CRLF - data - when "*" - objects = line.to_i - return nil if bulklen == -1 - res = [] - objects.times { - res << read_reply - } - res - else - raise "Protocol error, got '#{rtype}' as initial reply byte" - end - end -end -- 2.45.2