From 38210f7fe571c6b892f539d11d0c69ee1a93d00b Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 10 Jun 2009 00:08:10 +0200 Subject: [PATCH] Redis-rb sync --- client-libraries/ruby/lib/dist_redis.rb | 19 +- client-libraries/ruby/lib/pipeline.rb | 18 +- client-libraries/ruby/lib/redis.rb | 669 +++++++++++++++-------- client-libraries/ruby/redis-rb.gemspec | 2 +- client-libraries/ruby/spec/redis_spec.rb | 399 +++++++------- 5 files changed, 664 insertions(+), 443 deletions(-) diff --git a/client-libraries/ruby/lib/dist_redis.rb b/client-libraries/ruby/lib/dist_redis.rb index 830f8b6d..e79af472 100644 --- a/client-libraries/ruby/lib/dist_redis.rb +++ b/client-libraries/ruby/lib/dist_redis.rb @@ -2,20 +2,13 @@ require 'redis' require 'hash_ring' class DistRedis attr_reader :ring - def initialize(opts={}) - hosts = [] - - db = opts[:db] || nil - timeout = opts[:timeout] || nil - - raise Error, "No hosts given" unless opts[:hosts] - - opts[:hosts].each do |h| - host, port = h.split(':') - hosts << Redis.new(:host => host, :port => port, :db => db, :timeout => timeout, :db => db) + def initialize(*servers) + srvs = [] + servers.each do |s| + server, port = s.split(':') + srvs << Redis.new(:host => server, :port => port) end - - @ring = HashRing.new hosts + @ring = HashRing.new srvs end def node_for_key(key) diff --git a/client-libraries/ruby/lib/pipeline.rb b/client-libraries/ruby/lib/pipeline.rb index acab5acc..f92b96db 100644 --- a/client-libraries/ruby/lib/pipeline.rb +++ b/client-libraries/ruby/lib/pipeline.rb @@ -8,13 +8,19 @@ class Redis @redis = redis @commands = [] end - - def call_command(command) - @commands << command + + def execute_command(data) + @commands << data + write_and_read if @commands.size >= BUFFER_SIZE end - - def execute - @redis.call_command(@commands) + + def finish + write_and_read + end + + def write_and_read + @redis.execute_command(@commands.join, true) + @redis.read_socket @commands.clear end diff --git a/client-libraries/ruby/lib/redis.rb b/client-libraries/ruby/lib/redis.rb index 5c43bdd0..b10c42bf 100644 --- a/client-libraries/ruby/lib/redis.rb +++ b/client-libraries/ruby/lib/redis.rb @@ -1,273 +1,464 @@ require 'socket' +require 'set' +require File.join(File.dirname(__FILE__),'server') require File.join(File.dirname(__FILE__),'pipeline') -begin - if (RUBY_VERSION >= '1.9') - require 'timeout' - RedisTimer = Timeout - else - require 'system_timer' - RedisTimer = SystemTimer - end -rescue LoadError - RedisTimer = nil +class RedisError < StandardError +end +class RedisRenameError < StandardError end class Redis - 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", - "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 + 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 to_s - "Redis Client connected to #{@host}:#{@port} against DB #{@db}" - end + def pipelined + pipeline = Pipeline.new(self) + yield pipeline + pipeline.finish + end - def connect_to_server - @sock = connect_to(@host,@port,@timeout == 0 ? nil : @timeout) - call_command(["select",@db]) if @db != 0 - 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 connect_to(host, port, timeout=nil) - # We support connect() timeout only if system_timer is availabe - # or if we are running against Ruby >= 1.9 - # Timeout reading from the socket instead will be supported anyway. - if @timeout != 0 and RedisTimer - begin - sock = TCPSocket.new(host, port) - rescue Timeout::Error - @sock = nil - raise Timeout::Error, "Timeout connecting to the server" - end - else - sock = TCPSocket.new(host, port) - end - sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1 - - # If the timeout is set we set the low level socket options in order - # to make sure a blocking read will return after the specified number - # of seconds. This hack is from memcached ruby client. - if timeout - secs = Integer(timeout) - usecs = Integer((timeout - secs) * 1_000_000) - optval = [secs, usecs].pack("l_2") - sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval - sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval - end - sock - end + def ping + execute_command("PING\r\n") == PONG + end - def method_missing(*argv) - call_command(argv) - 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 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 + 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 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] + 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 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 select(*args) - raise "SELECT not allowed, use the :db option when creating the object" + 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 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 [](key) - get(key) + 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 end + end - def []=(key,value) - set(key,value) + 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" end + end - def set(key, value, expiry=nil) - call_command([:set, key, value]) - expire(key, expiry) unless expiry.nil? + 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" end + 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) + 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 end + end - def incr(key,increment=nil) - call_command(increment ? ["incrby",key,increment] : ["incr",key]) + 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" 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 set_move(srckey, destkey, member) + execute_command("SMOVE #{srckey} #{destkey} #{value_to_wire(member)}\r\n") == 1 + end - def decr(key,decrement=nil) - call_command(decrement ? ["decrby",key,decrement] : ["decr",key]) + 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 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 - - # Ruby defines a now deprecated type method so we need to override it here - # since it will never hit method_missing - def type(key) - call_command(['type', key]) + end + + def 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 end - - def quit - call_command(['quit']) - rescue Errno::ECONNRESET + list + end + + def get_reply + begin + r = read(1) + raise RedisError if (r == "\r" || r == "\n") + rescue RedisError + retry 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 + 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 pipelined(&block) - pipeline = Pipeline.new self - yield pipeline - pipeline.execute + 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) 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 + 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 - 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 + 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/redis-rb.gemspec b/client-libraries/ruby/redis-rb.gemspec index 2cd25211..e8d8b6eb 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.5" + 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 d4dde964..de302f34 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.del k} + @r.keys('*').each {|k| @r.delete k} end after(:all) do @@ -30,7 +30,7 @@ describe "redis" do end it 'should be able to PING' do - @r.ping.should == 'PONG' + @r.ping.should == true 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" do + it "should be able to SETNX(set_unless_exists)" do @r['foo'] = 'nik' @r['foo'].should == 'nik' - @r.setnx 'foo', 'bar' + @r.set_unless_exists 'foo', 'bar' @r['foo'].should == 'nik' end # - it "should be able to INCR a key" do - @r.del('counter') + it "should be able to INCR(increment) a key" do + @r.delete('counter') @r.incr('counter').should == 1 @r.incr('counter').should == 2 @r.incr('counter').should == 3 end # - it "should be able to DECR a key" do - @r.del('counter') + it "should be able to DECR(decrement) a key" do + @r.delete('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" do + it "should be able to RANDKEY(return a random key)" do @r.randkey.should_not be_nil end # it "should be able to RENAME a key" do - @r.del 'foo' - @r.del'bar' + @r.delete 'foo' + @r.delete 'bar' @r['foo'] = 'hi' - @r.rename 'foo', 'bar' + @r.rename! 'foo', 'bar' @r['bar'].should == 'hi' end # - it "should be able to RENAMENX a key" do - @r.del 'foo' - @r.del 'bar' + it "should be able to RENAMENX(rename unless the new key already exists) a key" do + @r.delete 'foo' + @r.delete 'bar' @r['foo'] = 'hi' @r['bar'] = 'ohai' - @r.renamenx 'foo', 'bar' + lambda {@r.rename 'foo', 'bar'}.should raise_error(RedisRenameError) @r['bar'].should == 'ohai' end # @@ -117,224 +117,251 @@ 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" do + it "should be able to EXISTS(check if key exists)" do @r['foo'] = 'nik' - @r.exists('foo').should be_true - @r.del 'foo' - @r.exists('foo').should be_false + @r.key?('foo').should be_true + @r.delete 'foo' + @r.key?('foo').should be_false end # - it "should be able to KEYS" do - @r.keys("f*").each { |key| @r.del key } + it "should be able to KEYS(glob for keys)" do + @r.keys("f*").each do |key| + @r.delete key + end @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.del 'foo' - @r.type('foo').should == "none" + @r.type?('foo').should == "string" + @r.delete 'foo' + @r.type?('foo').should == "none" end # - it "should be able to push to the head of a list (LPUSH)" do - @r.lpush "list", 'hello' - @r.lpush "list", 42 - @r.type('list').should == "list" - @r.llen('list').should == 2 - @r.lpop('list').should == '42' + 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') end # - it "should be able to push to the tail of a list (RPUSH)" do - @r.rpush "list", 'hello' - @r.type('list').should == "list" - @r.llen('list').should == 1 + 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') end # - it "should be able to pop the tail of a list (RPOP)" do - @r.rpush "list", 'hello' - @r.rpush"list", 'goodbye' - @r.type('list').should == "list" - @r.llen('list').should == 2 - @r.rpop('list').should == 'goodbye' + 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') end # - it "should be able to pop the head of a list (LPOP)" do - @r.rpush "list", 'hello' - @r.rpush "list", 'goodbye' - @r.type('list').should == "list" - @r.llen('list').should == 2 - @r.lpop('list').should == 'hello' + 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') end # - it "should be able to get the length of a list (LLEN)" do - @r.rpush "list", 'hello' - @r.rpush "list", 'goodbye' - @r.type('list').should == "list" - @r.llen('list').should == 2 + 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') end # - it "should be able to get a range of values from a list (LRANGE)" do - @r.rpush "list", 'hello' - @r.rpush "list", 'goodbye' - @r.rpush "list", '1' - @r.rpush "list", '2' - @r.rpush "list", '3' - @r.type('list').should == "list" - @r.llen('list').should == 5 - @r.lrange('list', 2, -1).should == ['1', '2', '3'] + 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') end # - it "should be able to trim a list (LTRIM)" do - @r.rpush "list", 'hello' - @r.rpush "list", 'goodbye' - @r.rpush "list", '1' - @r.rpush "list", '2' - @r.rpush "list", '3' - @r.type('list').should == "list" - @r.llen('list').should == 5 - @r.ltrim 'list', 0, 1 - @r.llen('list').should == 2 - @r.lrange('list', 0, -1).should == ['hello', 'goodbye'] + 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') end # - it "should be able to get a value by indexing into a list (LINDEX)" do - @r.rpush "list", 'hello' - @r.rpush "list", 'goodbye' - @r.type('list').should == "list" - @r.llen('list').should == 2 - @r.lindex('list', 1).should == 'goodbye' + 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') end # - it "should be able to set a value by indexing into a list (LSET)" do - @r.rpush "list", 'hello' - @r.rpush "list", 'hello' - @r.type('list').should == "list" - @r.llen('list').should == 2 - @r.lset('list', 1, 'goodbye').should == 'OK' - @r.lindex('list', 1).should == 'goodbye' + 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') end # - it "should be able to remove values from a list (LREM)" do - @r.rpush "list", 'hello' - @r.rpush "list", 'goodbye' - @r.type('list').should == "list" - @r.llen('list').should == 2 - @r.lrem('list', 1, 'hello').should == 1 - @r.lrange('list', 0, -1).should == ['goodbye'] + 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') end # - it "should be able add members to a set (SADD)" do - @r.sadd "set", 'key1' - @r.sadd "set", 'key2' - @r.type('set').should == "set" - @r.scard('set').should == 2 - @r.smembers('set').sort.should == ['key1', 'key2'].sort + 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') end # - it "should be able delete members to a set (SREM)" do - @r.sadd "set", 'key1' - @r.sadd "set", 'key2' - @r.type('set').should == "set" - @r.scard('set').should == 2 - @r.smembers('set').sort.should == ['key1', 'key2'].sort - @r.srem('set', 'key1') - @r.scard('set').should == 1 - @r.smembers('set').should == ['key2'] + 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') end # - it "should be able count the members of a set (SCARD)" do - @r.sadd "set", 'key1' - @r.sadd "set", 'key2' - @r.type('set').should == "set" - @r.scard('set').should == 2 + 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') end # - it "should be able test for set membership (SISMEMBER)" do - @r.sadd "set", 'key1' - @r.sadd "set", 'key2' - @r.type('set').should == "set" - @r.scard('set').should == 2 - @r.sismember('set', 'key1').should be_true - @r.sismember('set', 'key2').should be_true - @r.sismember('set', 'notthere').should be_false + 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') end # - it "should be able to do set intersection (SINTER)" do - @r.sadd "set", 'key1' - @r.sadd "set", 'key2' - @r.sadd "set2", 'key2' - @r.sinter('set', 'set2').should == ['key2'] + 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') end # - it "should be able to do set intersection and store the results in a key (SINTERSTORE)" do - @r.sadd "set", 'key1' - @r.sadd "set", 'key2' - @r.sadd "set2", 'key2' - @r.sinterstore('newone', 'set', 'set2').should == 1 - @r.smembers('newone').should == ['key2'] + 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') end # - it "should be able to do set union (SUNION)" do - @r.sadd "set", 'key1' - @r.sadd "set", 'key2' - @r.sadd "set2", 'key2' - @r.sadd "set2", 'key3' - @r.sunion('set', 'set2').sort.should == ['key1','key2','key3'].sort + 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') end # - it "should be able to do set union and store the results in a key (SUNIONSTORE)" do - @r.sadd "set", 'key1' - @r.sadd "set", 'key2' - @r.sadd "set2", 'key2' - @r.sadd "set2", 'key3' - @r.sunionstore('newone', 'set', 'set2').should == 3 - @r.smembers('newone').sort.should == ['key1','key2','key3'].sort + 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') end # - it "should be able to do set difference (SDIFF)" do - @r.sadd "set", 'a' - @r.sadd "set", 'b' - @r.sadd "set2", 'b' - @r.sadd "set2", 'c' - @r.sdiff('set', 'set2').should == ['a'] + 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 (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'] + 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 (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 + 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 @r.delete('set1') end # it "should be able to do crazy SORT queries" do @r['dog_1'] = 'louie' - @r.rpush 'dogs', 1 + @r.push_tail 'dogs', 1 @r['dog_2'] = 'lucy' - @r.rpush 'dogs', 2 + @r.push_tail 'dogs', 2 @r['dog_3'] = 'max' - @r.rpush 'dogs', 3 + @r.push_tail 'dogs', 3 @r['dog_4'] = 'taj' - @r.rpush 'dogs', 4 + @r.push_tail '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 @@ -342,16 +369,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.rpush 'dogs', 1 + @r.push_tail 'dogs', 1 @r['dog:2:name'] = 'lucy' @r['dog:2:breed'] = 'poodle' - @r.rpush 'dogs', 2 + @r.push_tail 'dogs', 2 @r['dog:3:name'] = 'max' @r['dog:3:breed'] = 'hound' - @r.rpush 'dogs', 3 + @r.push_tail 'dogs', 3 @r['dog:4:name'] = 'taj' @r['dog:4:breed'] = 'terrier' - @r.rpush 'dogs', 4 + @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 @@ -365,13 +392,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'].sort #foo from before - @r.flushdb + @r.keys('*').sort.should == ['foo', 'key1', 'key2'] #foo from before + @r.flush_db @r.keys('*').should == [] end # - it "should be able to provide the last save time (LASTSAVE)" do - savetime = @r.lastsave + it "should be able to provide the last save time" do + savetime = @r.last_save Time.at(savetime).class.should == Time Time.at(savetime).should <= Time.now end @@ -384,12 +411,13 @@ describe "redis" do end it "should bgsave" do - @r.bgsave.should == 'OK' + lambda {@r.bgsave}.should_not raise_error(RedisError) end it "should handle multiple servers" do require 'dist_redis' - @r = DistRedis.new(:hosts=> ['localhost:6379', '127.0.0.1:6379'], :db => 15) + @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 100.times do |idx| @r[idx] = "foo#{idx}" @@ -402,13 +430,16 @@ describe "redis" do it "should be able to pipeline writes" do @r.pipelined do |pipeline| - pipeline.lpush 'list', "hello" - pipeline.lpush 'list', 42 + pipeline.push_head "list", "hello" + pipeline.push_head "list", 42 end - @r.type('list').should == "list" - @r.llen('list').should == 2 - @r.lpop('list').should == '42' + @r.type?('list').should == "list" + @r.list_length('list').should == 2 + @r.pop_head('list').should == '42' + @r.delete('list') end + it "should select db on connection" + it "should re-select db on reconnection" end -- 2.45.2