lib/redis/connection/memory.rb in fakeredis-0.4.2 vs lib/redis/connection/memory.rb in fakeredis-0.4.3

- old
+ new

@@ -75,11 +75,11 @@ def timeout=(usecs) end def write(command) - meffod = command.shift + meffod = command.shift.to_s.downcase.to_sym if respond_to?(meffod) reply = send(meffod, *command) else raise Redis::CommandError, "ERR unknown command '#{meffod}'" end @@ -129,11 +129,11 @@ "OK" end def info { - "redis_version" => "0.07", + "redis_version" => "2.6.16", "connected_clients" => "1", "connected_slaves" => "0", "used_memory" => "3187", "changes_since_last_save" => "0", "last_save_time" => "1237655729", @@ -183,11 +183,11 @@ set(key, value) end end def mget(*keys) - raise Redis::CommandError, "wrong number of arguments for 'mget' command" if keys.empty? + raise_argument_error('mget') if keys.empty? # We work with either an array, or list of arguments keys = keys.first if keys.size == 1 data.values_at(*keys) end @@ -210,10 +210,11 @@ data_type_check(key, Hash) data[key] && data[key][field.to_s] end def hdel(key, field) + field = field.to_s data_type_check(key, Hash) data[key] && data[key].delete(field) remove_key_for_empty_collection(key) end @@ -222,12 +223,11 @@ return [] if data[key].nil? data[key].keys end def keys(pattern = "*") - regexp = Regexp.new(pattern.split("*").map { |r| Regexp.escape(r) }.join(".*")) - data.keys.select { |key| key =~ regexp } + data.keys.select { |key| File.fnmatch(pattern, key) } end def randomkey data.keys[rand(dbsize)] end @@ -264,11 +264,22 @@ end def ltrim(key, start, stop) data_type_check(key, Array) return unless data[key] - data[key] = data[key][start..stop] + + if start < 0 && data[key].count < start.abs + # Example: we have a list of 3 elements and + # we give it a ltrim list, -5, -1. This means + # it should trim to a max of 5. Since 3 < 5 + # we should not touch the list. This is consistent + # with behavior of real Redis's ltrim with a negative + # start argument. + data[key] + else + data[key] = data[key][start..stop] + end end def lindex(key, index) data_type_check(key, Array) data[key] && data[key][index] @@ -279,18 +290,18 @@ return unless data[key] index = data[key].index(pivot) case where when :before then data[key].insert(index, value) when :after then data[key].insert(index + 1, value) - else raise Redis::CommandError, "ERR syntax error" + else raise_syntax_error end end def lset(key, index, value) data_type_check(key, Array) return unless data[key] - raise(Redis::CommandError, "ERR index out of range") if index >= data[key].size + raise Redis::CommandError, "ERR index out of range" if index >= data[key].size data[key][index] = value end def lrem(key, count, value) data_type_check(key, Array) @@ -372,10 +383,11 @@ end def sadd(key, value) data_type_check(key, ::Set) value = Array(value) + raise_argument_error('sadd') if value.empty? result = if data[key] old_set = data[key].dup data[key].merge(value.map(&:to_s)) (data[key] - old_set).size @@ -415,10 +427,12 @@ return 0 unless data[key] data[key].size end def sinter(*keys) + raise_argument_error('sinter') if keys.empty? + keys.each { |k| data_type_check(k, ::Set) } return ::Set.new if keys.any? { |k| data[k].nil? } keys = keys.map { |k| data[k] || ::Set.new } keys.inject do |set, key| set & key @@ -446,11 +460,11 @@ end def sdiff(key1, *keys) [key1, *keys].each { |k| data_type_check(k, ::Set) } keys = keys.map { |k| data[k] || ::Set.new } - keys.inject(data[key1]) do |memo, set| + keys.inject(data[key1] || Set.new) do |memo, set| memo - set end.to_a end def sdiffstore(destination, key1, *keys) @@ -465,11 +479,12 @@ data[key].to_a[rand(data[key].size)] end def del(*keys) keys = keys.flatten(1) - raise Redis::CommandError, "ERR wrong number of arguments for 'del' command" if keys.empty? + raise_argument_error('del') if keys.empty? + old_count = data.keys.size keys.each do |key| data.delete(key) end old_count - data.keys.size @@ -508,11 +523,11 @@ def ttl(key) if data.expires.include?(key) && (ttl = data.expires[key].to_i - Time.now.to_i) > 0 ttl else - -1 + exists(key) ? -1 : -2 end end def expireat(key, timestamp) data.expires[key] = Time.at(timestamp) @@ -544,20 +559,34 @@ end def hmset(key, *fields) # mapped_hmset gives us [[:k1, "v1", :k2, "v2"]] for `fields`. Fix that. fields = fields[0] if mapped_param?(fields) - raise Redis::CommandError, "ERR wrong number of arguments for HMSET" if fields.empty? || fields.size.odd? + raise_argument_error('hmset') if fields.empty? + + is_list_of_arrays = fields.all?{|field| field.instance_of?(Array)} + + raise_argument_error('hmset') if fields.size.odd? and !is_list_of_arrays + raise_argument_error('hmset') if is_list_of_arrays and !fields.all?{|field| field.length == 2} + data_type_check(key, Hash) data[key] ||= {} - fields.each_slice(2) do |field| - data[key][field[0].to_s] = field[1].to_s + + if is_list_of_arrays + fields.each do |pair| + data[key][pair[0].to_s] = pair[1].to_s + end + else + fields.each_slice(2) do |field| + data[key][field[0].to_s] = field[1].to_s + end end end def hmget(key, *fields) - raise Redis::CommandError, "wrong number of arguments for 'hmget' command" if fields.empty? + raise_argument_error('hmget') if fields.empty? + data_type_check(key, Hash) fields.map do |field| field = field.to_s if data[key] data[key][field] @@ -579,22 +608,23 @@ data[key].values end def hincrby(key, field, increment) data_type_check(key, Hash) + field = field.to_s if data[key] - data[key][field] = (data[key][field.to_s].to_i + increment.to_i).to_s + data[key][field] = (data[key][field].to_i + increment.to_i).to_s else data[key] = { field => increment.to_s } end data[key][field].to_i end def hexists(key, field) data_type_check(key, Hash) return false unless data[key] - data[key].key?(field) + data[key].key?(field.to_s) end def sync ; end def [](key) @@ -623,10 +653,11 @@ end def setex(key, seconds, value) data[key] = value.to_s expire(key, seconds) + "OK" end def setrange(key, offset, value) return unless data[key] s = data[key][offset,value.size] @@ -634,10 +665,14 @@ end def mset(*pairs) # Handle pairs for mapped_mset command pairs = pairs[0] if mapped_param?(pairs) + raise_argument_error('mset') if pairs.empty? || pairs.size == 1 + # We have to reply with a different error message here to be consistent with redis-rb 3.0.6 / redis-server 2.8.1 + raise_argument_error("mset", "mset_odd") if pairs.size.odd? + pairs.each_slice(2) do |pair| data[pair[0].to_s] = pair[1].to_s end "OK" end @@ -678,10 +713,11 @@ def type(key) case data[key] when nil then "none" when String then "string" + when ZSet then "zset" when Hash then "hash" when Array then "list" when ::Set then "set" end end @@ -711,24 +747,24 @@ end def zadd(key, *args) if !args.first.is_a?(Array) if args.size < 2 - raise Redis::CommandError, "ERR wrong number of arguments for 'zadd' command" + raise_argument_error('zadd') elsif args.size.odd? - raise Redis::CommandError, "ERR syntax error" + raise_syntax_error end else unless args.all? {|pair| pair.size == 2 } - raise(Redis::CommandError, "ERR syntax error") + raise_syntax_error end end data_type_check(key, ZSet) data[key] ||= ZSet.new - if args.size == 2 + if args.size == 2 && !(Array === args.first) score, value = args exists = !data[key].key?(value.to_s) data[key][value.to_s] = score else # Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already @@ -744,11 +780,11 @@ data_type_check(key, ZSet) values = Array(value) return 0 unless data[key] response = values.map do |v| - data[key].delete(v) if data[key].has_key?(v) + data[key].delete(v.to_s) if data[key].has_key?(v.to_s) end.compact.size remove_key_for_empty_collection(key) response end @@ -778,16 +814,20 @@ data[key][value.to_s].to_s end def zrank(key, value) data_type_check(key, ZSet) - data[key].keys.sort_by {|k| data[key][k] }.index(value.to_s) + z = data[key] + return unless z + z.keys.sort_by {|k| z[k] }.index(value.to_s) end def zrevrank(key, value) data_type_check(key, ZSet) - data[key].keys.sort_by {|k| -data[key][k] }.index(value.to_s) + z = data[key] + return unless z + z.keys.sort_by {|k| -z[k] }.index(value.to_s) end def zrange(key, start, stop, with_scores = nil) data_type_check(key, ZSet) return [] unless data[key] @@ -832,10 +872,11 @@ vals.flatten.map(&:to_s) end def zrevrangebyscore(key, max, min, *opts) + opts = opts.flatten data_type_check(key, ZSet) return [] unless data[key] range = data[key].select_by_score(min, max) vals = if opts.include?('WITHSCORES') @@ -880,18 +921,31 @@ elements_to_delete.each { |elem, rank| data[key].delete(elem) } elements_to_delete.size end private + def raise_argument_error(command, match_string=command) + error_message = if %w(hmset mset_odd).include?(match_string.downcase) + "ERR wrong number of arguments for #{command.upcase}" + else + "ERR wrong number of arguments for '#{command}' command" + end + raise Redis::CommandError, error_message + end + + def raise_syntax_error + raise Redis::CommandError, "ERR syntax error" + end + def remove_key_for_empty_collection(key) del(key) if data[key] && data[key].empty? end def data_type_check(key, klass) if data[key] && !data[key].is_a?(klass) warn "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}." - raise Redis::CommandError.new("ERR Operation against a key holding the wrong kind of value") + raise Redis::CommandError.new("WRONGTYPE Operation against a key holding the wrong kind of value") end end def get_limit(opts, vals) index = opts.index('LIMIT')