lib/redis/connection/memory.rb in fakeredis-0.4.1 vs lib/redis/connection/memory.rb in fakeredis-0.4.2
- old
+ new
@@ -1,146 +1,135 @@
require 'set'
require 'redis/connection/registry'
require 'redis/connection/command_helper'
+require "fakeredis/expiring_hash"
+require "fakeredis/sorted_set_argument_handler"
+require "fakeredis/sorted_set_store"
+require "fakeredis/zset"
class Redis
module Connection
class Memory
- # Represents a normal hash with some additional expiration information
- # associated with each key
- class ExpiringHash < Hash
- attr_reader :expires
+ include Redis::Connection::CommandHelper
+ include FakeRedis
- def initialize(*)
- super
- @expires = {}
- end
+ attr_accessor :buffer, :options
- def [](key)
- delete(key) if expired?(key)
- super
- end
+ # Tracks all databases for all instances across the current process.
+ # We have to be able to handle two clients with the same host/port accessing
+ # different databases at once without overwriting each other. So we store our
+ # "data" outside the client instances, in this class level instance method.
+ # Client instances access it with a key made up of their host/port, and then select
+ # which DB out of the array of them they want. Allows the access we need.
+ def self.databases
+ @databases ||= Hash.new {|h,k| h[k] = [] }
+ end
- def []=(key, val)
- expire(key)
- super
- end
+ # Used for resetting everything in specs
+ def self.reset_all_databases
+ @databases = nil
+ end
- def delete(key)
- expire(key)
- super
- end
+ def self.connect(options = {})
+ new(options)
+ end
- def expire(key)
- expires.delete(key)
- end
+ def initialize(options = {})
+ self.options = options
+ end
- def expired?(key)
- expires.include?(key) && expires[key] < Time.now
- end
+ def database_id
+ @database_id ||= 0
+ end
+ attr_writer :database_id
- def key?(key)
- delete(key) if expired?(key)
- super
- end
-
- def values_at(*keys)
- keys.each {|key| delete(key) if expired?(key)}
- super
- end
-
- def keys
- super.select do |key|
- if expired?(key)
- delete(key)
- false
- else
- true
- end
- end
- end
+ def database_instance_key
+ [options[:host], options[:port]].hash
end
- class ZSet < Hash
+ def databases
+ self.class.databases[database_instance_key]
end
- include Redis::Connection::CommandHelper
-
- def initialize(connected = false)
- @data = ExpiringHash.new
- @connected = connected
- @replies = []
- @buffer = nil
+ def find_database id=database_id
+ databases[id] ||= ExpiringHash.new
end
- def connected?
- @connected
+ def data
+ find_database
end
- def self.instances
- @instances ||= {}
+ def replies
+ @replies ||= []
end
+ attr_writer :replies
- def self.connect(options = {})
- self.instances[options] ||= self.new(true)
+ def connected?
+ true
end
def connect_unix(path, timeout)
- @connected = true
end
def disconnect
- @connected = false
- nil
end
def timeout=(usecs)
end
def write(command)
- method = command.shift
- reply = send(method, *command)
+ meffod = command.shift
+ if respond_to?(meffod)
+ reply = send(meffod, *command)
+ else
+ raise Redis::CommandError, "ERR unknown command '#{meffod}'"
+ end
if reply == true
reply = 1
elsif reply == false
reply = 0
end
- @replies << reply
- @buffer << reply if @buffer && method != :multi
+ replies << reply
+ buffer << reply if buffer && meffod != :multi
nil
end
def read
- @replies.shift
+ replies.shift
end
# NOT IMPLEMENTED:
# * blpop
# * brpop
# * brpoplpush
# * discard
- # * move
+ # * sort
# * subscribe
# * psubscribe
# * publish
- # * zremrangebyrank
- # * zunionstore
+
def flushdb
- @data = ExpiringHash.new
+ databases.delete_at(database_id)
+ "OK"
end
def flushall
- flushdb
+ self.class.databases[database_instance_key] = []
+ "OK"
end
def auth(password)
"OK"
end
- def select(index) ; end
+ def select(index)
+ data_type_check(index, Integer)
+ self.database_id = index
+ "OK"
+ end
def info
{
"redis_version" => "0.07",
"connected_clients" => "1",
@@ -161,75 +150,88 @@
def bgsave ; end
def bgreriteaof ; end
+ def move key, destination_id
+ raise Redis::CommandError, "ERR source and destination objects are the same" if destination_id == database_id
+ destination = find_database(destination_id)
+ return false unless data.has_key?(key)
+ return false if destination.has_key?(key)
+ destination[key] = data.delete(key)
+ true
+ end
+
def get(key)
- @data[key]
+ data_type_check(key, String)
+ data[key]
end
def getbit(key, offset)
- return unless @data[key]
- @data[key].unpack('B*')[0].split("")[offset].to_i
+ return unless data[key]
+ data[key].unpack('B*')[0].split("")[offset].to_i
end
def getrange(key, start, ending)
- return unless @data[key]
- @data[key][start..ending]
+ return unless data[key]
+ data[key][start..ending]
end
alias :substr :getrange
def getset(key, value)
- old_value = @data[key]
- @data[key] = value
- return old_value
+ data_type_check(key, String)
+ data[key].tap do
+ set(key, value)
+ end
end
def mget(*keys)
raise Redis::CommandError, "wrong number of arguments for 'mget' command" if keys.empty?
- @data.values_at(*keys)
+ # We work with either an array, or list of arguments
+ keys = keys.first if keys.size == 1
+ data.values_at(*keys)
end
def append(key, value)
- @data[key] = (@data[key] || "")
- @data[key] = @data[key] + value.to_s
+ data[key] = (data[key] || "")
+ data[key] = data[key] + value.to_s
end
def strlen(key)
- return unless @data[key]
- @data[key].size
+ return unless data[key]
+ data[key].size
end
def hgetall(key)
data_type_check(key, Hash)
- @data[key].to_a.flatten || {}
+ data[key].to_a.flatten || {}
end
def hget(key, field)
data_type_check(key, Hash)
- @data[key] && @data[key][field.to_s]
+ data[key] && data[key][field.to_s]
end
def hdel(key, field)
data_type_check(key, Hash)
- @data[key] && @data[key].delete(field)
+ data[key] && data[key].delete(field)
remove_key_for_empty_collection(key)
end
def hkeys(key)
data_type_check(key, Hash)
- return [] if @data[key].nil?
- @data[key].keys
+ 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| key =~ regexp }
end
def randomkey
- @data.keys[rand(dbsize)]
+ data.keys[rand(dbsize)]
end
def echo(string)
string
end
@@ -241,156 +243,157 @@
def lastsave
Time.now.to_i
end
def dbsize
- @data.keys.count
+ data.keys.count
end
def exists(key)
- @data.key?(key)
+ data.key?(key)
end
def llen(key)
data_type_check(key, Array)
- return 0 unless @data[key]
- @data[key].size
+ return 0 unless data[key]
+ data[key].size
end
def lrange(key, startidx, endidx)
data_type_check(key, Array)
- (@data[key] && @data[key][startidx..endidx]) || []
+ (data[key] && data[key][startidx..endidx]) || []
end
def ltrim(key, start, stop)
data_type_check(key, Array)
- return unless @data[key]
- @data[key] = @data[key][start..stop]
+ return unless data[key]
+ data[key] = data[key][start..stop]
end
def lindex(key, index)
data_type_check(key, Array)
- @data[key] && @data[key][index]
+ data[key] && data[key][index]
end
def linsert(key, where, pivot, value)
data_type_check(key, Array)
- return unless @data[key]
- index = @data[key].index(pivot)
+ 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)
+ when :before then data[key].insert(index, value)
+ when :after then data[key].insert(index + 1, value)
else raise Redis::CommandError, "ERR syntax error"
end
end
def lset(key, index, value)
data_type_check(key, Array)
- return unless @data[key]
- raise RuntimeError if index >= @data[key].size
- @data[key][index] = value
+ return unless data[key]
+ 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)
- return unless @data[key]
- old_size = @data[key].size
+ return unless data[key]
+ old_size = data[key].size
diff =
if count == 0
- @data[key].delete(value)
- old_size - @data[key].size
+ data[key].delete(value)
+ old_size - data[key].size
else
- array = count > 0 ? @data[key].dup : @data[key].reverse
+ array = count > 0 ? data[key].dup : data[key].reverse
count.abs.times{ array.delete_at(array.index(value) || array.length) }
- @data[key] = count > 0 ? array.dup : array.reverse
- old_size - @data[key].size
+ data[key] = count > 0 ? array.dup : array.reverse
+ old_size - data[key].size
end
remove_key_for_empty_collection(key)
diff
end
def rpush(key, value)
data_type_check(key, Array)
- @data[key] ||= []
+ data[key] ||= []
[value].flatten.each do |val|
- @data[key].push(val.to_s)
+ data[key].push(val.to_s)
end
- @data[key].size
+ data[key].size
end
def rpushx(key, value)
data_type_check(key, Array)
- return unless @data[key]
+ return unless data[key]
rpush(key, value)
end
def lpush(key, value)
data_type_check(key, Array)
- @data[key] ||= []
+ data[key] ||= []
[value].flatten.each do |val|
- @data[key].unshift(val.to_s)
+ data[key].unshift(val.to_s)
end
- @data[key].size
+ data[key].size
end
def lpushx(key, value)
data_type_check(key, Array)
- return unless @data[key]
+ return unless data[key]
lpush(key, value)
end
def rpop(key)
data_type_check(key, Array)
- return unless @data[key]
- @data[key].pop
+ return unless data[key]
+ data[key].pop
end
def rpoplpush(key1, key2)
data_type_check(key1, Array)
- elem = rpop(key1)
- lpush(key2, elem)
+ rpop(key1).tap do |elem|
+ lpush(key2, elem)
+ end
end
def lpop(key)
data_type_check(key, Array)
- return unless @data[key]
- @data[key].shift
+ return unless data[key]
+ data[key].shift
end
def smembers(key)
data_type_check(key, ::Set)
- return [] unless @data[key]
- @data[key].to_a.reverse
+ return [] unless data[key]
+ data[key].to_a.reverse
end
def sismember(key, value)
data_type_check(key, ::Set)
- return false unless @data[key]
- @data[key].include?(value.to_s)
+ return false unless data[key]
+ data[key].include?(value.to_s)
end
def sadd(key, value)
data_type_check(key, ::Set)
value = Array(value)
- result = if @data[key]
- old_set = @data[key].dup
- @data[key].merge(value.map(&:to_s))
- (@data[key] - old_set).size
+ result = if data[key]
+ old_set = data[key].dup
+ data[key].merge(value.map(&:to_s))
+ (data[key] - old_set).size
else
- @data[key] = ::Set.new(value.map(&:to_s))
- @data[key].size
+ data[key] = ::Set.new(value.map(&:to_s))
+ data[key].size
end
# 0 = false, 1 = true, 2+ untouched
return result == 1 if result < 2
result
end
def srem(key, value)
data_type_check(key, ::Set)
- deleted = !!(@data[key] && @data[key].delete?(value.to_s))
+ deleted = !!(data[key] && data[key].delete?(value.to_s))
remove_key_for_empty_collection(key)
deleted
end
def smove(source, destination, value)
@@ -407,71 +410,71 @@
elem
end
def scard(key)
data_type_check(key, ::Set)
- return 0 unless @data[key]
- @data[key].size
+ return 0 unless data[key]
+ data[key].size
end
def sinter(*keys)
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 }
+ 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
end.to_a
end
def sinterstore(destination, *keys)
data_type_check(destination, ::Set)
result = sinter(*keys)
- @data[destination] = ::Set.new(result)
+ data[destination] = ::Set.new(result)
end
def sunion(*keys)
keys.each { |k| data_type_check(k, ::Set) }
- keys = keys.map { |k| @data[k] || ::Set.new }
+ keys = keys.map { |k| data[k] || ::Set.new }
keys.inject(::Set.new) do |set, key|
set | key
end.to_a
end
def sunionstore(destination, *keys)
data_type_check(destination, ::Set)
result = sunion(*keys)
- @data[destination] = ::Set.new(result)
+ data[destination] = ::Set.new(result)
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 = keys.map { |k| data[k] || ::Set.new }
+ keys.inject(data[key1]) do |memo, set|
memo - set
end.to_a
end
def sdiffstore(destination, key1, *keys)
data_type_check(destination, ::Set)
result = sdiff(key1, *keys)
- @data[destination] = ::Set.new(result)
+ data[destination] = ::Set.new(result)
end
def srandmember(key)
data_type_check(key, ::Set)
- return nil unless @data[key]
- @data[key].to_a[rand(@data[key].size)]
+ return nil unless data[key]
+ 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?
- old_count = @data.keys.size
+ old_count = data.keys.size
keys.each do |key|
- @data.delete(key)
+ data.delete(key)
end
- deleted_count = old_count - @data.keys.size
+ old_count - data.keys.size
end
def setnx(key, value)
if exists(key)
false
@@ -480,14 +483,14 @@
true
end
end
def rename(key, new_key)
- return unless @data[key]
- @data[new_key] = @data[key]
- @data.expires[new_key] = @data.expires[key] if @data.expires.include?(key)
- @data.delete(key)
+ return unless data[key]
+ data[new_key] = data[key]
+ data.expires[new_key] = data.expires[key] if data.expires.include?(key)
+ data.delete(key)
end
def renamenx(key, new_key)
if exists(new_key)
false
@@ -496,101 +499,102 @@
true
end
end
def expire(key, ttl)
- return unless @data[key]
- @data.expires[key] = Time.now + ttl
+ return unless data[key]
+ data.expires[key] = Time.now + ttl
true
end
def ttl(key)
- if @data.expires.include?(key) && (ttl = @data.expires[key].to_i - Time.now.to_i) > 0
+ if data.expires.include?(key) && (ttl = data.expires[key].to_i - Time.now.to_i) > 0
ttl
else
-1
end
end
def expireat(key, timestamp)
- @data.expires[key] = Time.at(timestamp)
+ data.expires[key] = Time.at(timestamp)
true
end
def persist(key)
- !!@data.expires.delete(key)
+ !!data.expires.delete(key)
end
def hset(key, field, value)
data_type_check(key, Hash)
field = field.to_s
- if @data[key]
- result = !@data[key].include?(field)
- @data[key][field] = value.to_s
+ if data[key]
+ result = !data[key].include?(field)
+ data[key][field] = value.to_s
result
else
- @data[key] = { field => value.to_s }
+ data[key] = { field => value.to_s }
true
end
end
def hsetnx(key, field, value)
data_type_check(key, Hash)
field = field.to_s
- return false if @data[key] && @data[key][field]
+ return false if data[key] && data[key][field]
hset(key, field, value)
end
def hmset(key, *fields)
- raise Redis::CommandError, "wrong number of arguments for 'hmset' command" if fields.empty? || fields.size.odd?
+ # 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?
data_type_check(key, Hash)
- @data[key] ||= {}
+ data[key] ||= {}
fields.each_slice(2) do |field|
- @data[key][field[0].to_s] = field[1].to_s
+ data[key][field[0].to_s] = field[1].to_s
end
end
def hmget(key, *fields)
raise Redis::CommandError, "wrong number of arguments for 'hmget' command" if fields.empty?
data_type_check(key, Hash)
- values = []
fields.map do |field|
field = field.to_s
- if @data[key]
- @data[key][field]
+ if data[key]
+ data[key][field]
else
nil
end
end
end
def hlen(key)
data_type_check(key, Hash)
- return 0 unless @data[key]
- @data[key].size
+ return 0 unless data[key]
+ data[key].size
end
def hvals(key)
data_type_check(key, Hash)
- return [] unless @data[key]
- @data[key].values
+ return [] unless data[key]
+ data[key].values
end
def hincrby(key, field, increment)
data_type_check(key, Hash)
- if @data[key]
- @data[key][field] = (@data[key][field.to_s].to_i + increment.to_i).to_s
+ if data[key]
+ data[key][field] = (data[key][field.to_s].to_i + increment.to_i).to_s
else
- @data[key] = { field => increment.to_s }
+ data[key] = { field => increment.to_s }
end
- @data[key][field].to_i
+ data[key][field].to_i
end
def hexists(key, field)
data_type_check(key, Hash)
- return false unless @data[key]
- @data[key].key?(field)
+ return false unless data[key]
+ data[key].key?(field)
end
def sync ; end
def [](key)
@@ -600,78 +604,82 @@
def []=(key, value)
set(key, value)
end
def set(key, value)
- @data[key] = value.to_s
+ data[key] = value.to_s
"OK"
end
def setbit(key, offset, bit)
- old_val = @data[key] ? @data[key].unpack('B*')[0].split("") : []
+ old_val = data[key] ? data[key].unpack('B*')[0].split("") : []
size_increment = [((offset/8)+1)*8-old_val.length, 0].max
old_val += Array.new(size_increment).map{"0"}
original_val = old_val[offset]
old_val[offset] = bit.to_s
new_val = ""
old_val.each_slice(8){|b| new_val = new_val + b.join("").to_i(2).chr }
- @data[key] = new_val
+ data[key] = new_val
original_val
end
def setex(key, seconds, value)
- @data[key] = value.to_s
+ data[key] = value.to_s
expire(key, seconds)
end
def setrange(key, offset, value)
- return unless @data[key]
- s = @data[key][offset,value.size]
- @data[key][s] = value
+ return unless data[key]
+ s = data[key][offset,value.size]
+ data[key][s] = value
end
def mset(*pairs)
+ # Handle pairs for mapped_mset command
+ pairs = pairs[0] if mapped_param?(pairs)
pairs.each_slice(2) do |pair|
- @data[pair[0].to_s] = pair[1].to_s
+ data[pair[0].to_s] = pair[1].to_s
end
"OK"
end
def msetnx(*pairs)
+ # Handle pairs for mapped_msetnx command
+ pairs = pairs[0] if mapped_param?(pairs)
keys = []
pairs.each_with_index{|item, index| keys << item.to_s if index % 2 == 0}
- return if keys.any?{|key| @data.key?(key) }
+ return false if keys.any?{|key| data.key?(key) }
mset(*pairs)
true
end
def sort(key)
# TODO: Implement
end
def incr(key)
- @data.merge!({ key => (@data[key].to_i + 1).to_s || "1"})
- @data[key].to_i
+ data.merge!({ key => (data[key].to_i + 1).to_s || "1"})
+ data[key].to_i
end
def incrby(key, by)
- @data.merge!({ key => (@data[key].to_i + by.to_i).to_s || by })
- @data[key].to_i
+ data.merge!({ key => (data[key].to_i + by.to_i).to_s || by })
+ data[key].to_i
end
def decr(key)
- @data.merge!({ key => (@data[key].to_i - 1).to_s || "-1"})
- @data[key].to_i
+ data.merge!({ key => (data[key].to_i - 1).to_s || "-1"})
+ data[key].to_i
end
def decrby(key, by)
- @data.merge!({ key => ((@data[key].to_i - by.to_i) || (by.to_i * -1)).to_s })
- @data[key].to_i
+ data.merge!({ key => ((data[key].to_i - by.to_i) || (by.to_i * -1)).to_s })
+ data[key].to_i
end
def type(key)
- case value = @data[key]
+ case data[key]
when nil then "none"
when String then "string"
when Hash then "hash"
when Array then "list"
when ::Set then "set"
@@ -683,17 +691,15 @@
def shutdown; end
def slaveof(host, port) ; end
def exec
- buffer = @buffer
- @buffer = nil
- buffer
+ buffer.tap {|x| self.buffer = nil }
end
def multi
- @buffer = []
+ self.buffer = []
yield if block_given?
"OK"
end
def watch(_)
@@ -716,95 +722,107 @@
raise(Redis::CommandError, "ERR syntax error")
end
end
data_type_check(key, ZSet)
- @data[key] ||= ZSet.new
+ data[key] ||= ZSet.new
if args.size == 2
score, value = args
- exists = !@data[key].key?(value.to_s)
- @data[key][value.to_s] = score
+ 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
args = args.each_slice(2).to_a unless args.first.is_a?(Array)
- exists = args.map(&:last).map { |el| @data[key].key?(el.to_s) }.count(false)
- args.each { |score, value| @data[key][value.to_s] = score }
+ exists = args.map(&:last).map { |el| data[key].key?(el.to_s) }.count(false)
+ args.each { |s, v| data[key][v.to_s] = s }
end
exists
end
def zrem(key, value)
data_type_check(key, ZSet)
- exists = false
- exists = @data[key].delete(value.to_s) if @data[key]
+ values = Array(value)
+ return 0 unless data[key]
+
+ response = values.map do |v|
+ data[key].delete(v) if data[key].has_key?(v)
+ end.compact.size
+
remove_key_for_empty_collection(key)
- !!exists
+ response
end
def zcard(key)
data_type_check(key, ZSet)
- @data[key] ? @data[key].size : 0
+ data[key] ? data[key].size : 0
end
def zscore(key, value)
data_type_check(key, ZSet)
- @data[key] && @data[key][value.to_s].to_s
+ value = data[key] && data[key][value.to_s]
+ value && value.to_s
end
def zcount(key, min, max)
data_type_check(key, ZSet)
- return 0 unless @data[key]
- zrange_select_by_score(key, min, max).size
+ return 0 unless data[key]
+ data[key].select_by_score(min, max).size
end
def zincrby(key, num, value)
data_type_check(key, ZSet)
- @data[key] ||= ZSet.new
- @data[key][value.to_s] ||= 0
- @data[key][value.to_s] += num
- @data[key][value.to_s].to_s
+ data[key] ||= ZSet.new
+ data[key][value.to_s] ||= 0
+ data[key].increment(value.to_s, num)
+ 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)
+ data[key].keys.sort_by {|k| data[key][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)
+ data[key].keys.sort_by {|k| -data[key][k] }.index(value.to_s)
end
def zrange(key, start, stop, with_scores = nil)
data_type_check(key, ZSet)
- return [] unless @data[key]
+ return [] unless data[key]
- if with_scores
- @data[key].sort_by {|_,v| v }
- else
- @data[key].keys.sort_by {|k| @data[key][k] }
- end[start..stop].flatten.map(&:to_s)
+ # Sort by score, or if scores are equal, key alphanum
+ results = data[key].sort do |(k1, v1), (k2, v2)|
+ if v1 == v2
+ k1 <=> k2
+ else
+ v1 <=> v2
+ end
+ end
+ # Select just the keys unless we want scores
+ results = results.map(&:first) unless with_scores
+ results[start..stop].flatten.map(&:to_s)
end
def zrevrange(key, start, stop, with_scores = nil)
data_type_check(key, ZSet)
- return [] unless @data[key]
+ return [] unless data[key]
if with_scores
- @data[key].sort_by {|_,v| -v }
+ data[key].sort_by {|_,v| -v }
else
- @data[key].keys.sort_by {|k| -@data[key][k] }
+ data[key].keys.sort_by {|k| -data[key][k] }
end[start..stop].flatten.map(&:to_s)
end
def zrangebyscore(key, min, max, *opts)
data_type_check(key, ZSet)
- return [] unless @data[key]
+ return [] unless data[key]
- range = zrange_select_by_score(key, min, max)
+ range = data[key].select_by_score(min, max)
vals = if opts.include?('WITHSCORES')
range.sort_by {|_,v| v }
else
range.keys.sort_by {|k| range[k] }
end
@@ -815,13 +833,13 @@
vals.flatten.map(&:to_s)
end
def zrevrangebyscore(key, max, min, *opts)
data_type_check(key, ZSet)
- return [] unless @data[key]
+ return [] unless data[key]
- range = zrange_select_by_score(key, min, max)
+ range = data[key].select_by_score(min, max)
vals = if opts.include?('WITHSCORES')
range.sort_by {|_,v| -v }
else
range.keys.sort_by {|k| -range[k] }
end
@@ -832,70 +850,49 @@
vals.flatten.map(&:to_s)
end
def zremrangebyscore(key, min, max)
data_type_check(key, ZSet)
- return 0 unless @data[key]
+ return 0 unless data[key]
- range = zrange_select_by_score(key, min, max)
- range.each {|k,_| @data[key].delete(k) }
+ range = data[key].select_by_score(min, max)
+ range.each {|k,_| data[key].delete(k) }
range.size
end
- def zinterstore(out, _, *keys)
+ def zinterstore(out, *args)
data_type_check(out, ZSet)
+ args_handler = SortedSetArgumentHandler.new(args)
+ data[out] = SortedSetIntersectStore.new(args_handler, data).call
+ data[out].size
+ end
- hashes = keys.map do |src|
- case @data[src]
- when ::Set
- # Every value has a score of 1
- Hash[@data[src].map {|k,v| [k, 1]}]
- when Hash
- @data[src]
- else
- {}
- end
- end
-
- @data[out] = ZSet.new
- values = hashes.inject([]) {|r, h| r.empty? ? h.keys : r & h.keys }
- values.each do |value|
- @data[out][value] = hashes.inject(0) {|n, h| n + h[value].to_i }
- end
-
- @data[out].size
+ def zunionstore(out, *args)
+ data_type_check(out, ZSet)
+ args_handler = SortedSetArgumentHandler.new(args)
+ data[out] = SortedSetUnionStore.new(args_handler, data).call
+ data[out].size
end
def zremrangebyrank(key, start, stop)
- sorted_elements = @data[key].sort { |(v_a, r_a), (v_b, r_b)| r_a <=> r_b }
+ sorted_elements = data[key].sort_by { |k, v| v }
start = sorted_elements.length if start > sorted_elements.length
elements_to_delete = sorted_elements[start..stop]
- elements_to_delete.each { |elem, rank| @data[key].delete(elem) }
+ elements_to_delete.each { |elem, rank| data[key].delete(elem) }
elements_to_delete.size
end
private
- def zrange_select_by_score(key, min, max)
- if min == '-inf' && max == '+inf'
- @data[key]
- elsif max == '+inf'
- @data[key].reject { |_,v| v < min }
- elsif min == '-inf'
- @data[key].reject { |_,v| v > max }
- else
- @data[key].reject {|_,v| v < min || v > max }
- end
- end
-
def remove_key_for_empty_collection(key)
- del(key) if @data[key] && @data[key].empty?
+ del(key) if data[key] && data[key].empty?
end
def data_type_check(key, klass)
- if @data[key] && !@data[key].is_a?(klass)
- fail "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}."
+ 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")
end
end
def get_limit(opts, vals)
index = opts.index('LIMIT')
@@ -906,9 +903,13 @@
count = opts[index + 2]
count = vals.size if count < 0
[offset, count]
end
+ end
+
+ def mapped_param? param
+ param.size == 1 && param[0].is_a?(Array)
end
end
end
end