fakeredis 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +2 -2
- data/LICENSE +1 -1
- data/README.md +3 -2
- data/fakeredis.gemspec +2 -3
- data/lib/fake_redis.rb +1 -0
- data/lib/fakeredis/expiring_hash.rb +70 -0
- data/lib/fakeredis/rspec.rb +1 -1
- data/lib/fakeredis/sorted_set_argument_handler.rb +74 -0
- data/lib/fakeredis/sorted_set_store.rb +80 -0
- data/lib/fakeredis/version.rb +1 -1
- data/lib/fakeredis/zset.rb +29 -0
- data/lib/redis/connection/memory.rb +280 -279
- data/spec/compatibility_spec.rb +2 -2
- data/spec/connection_spec.rb +62 -1
- data/spec/hashes_spec.rb +40 -29
- data/spec/keys_spec.rb +105 -25
- data/spec/lists_spec.rb +34 -31
- data/spec/server_spec.rb +80 -2
- data/spec/sets_spec.rb +18 -18
- data/spec/sorted_sets_spec.rb +246 -62
- data/spec/spec_helper_live_redis.rb +1 -1
- data/spec/strings_spec.rb +79 -55
- data/spec/transactions_spec.rb +1 -1
- metadata +12 -7
data/.travis.yml
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# FakeRedis [](http://travis-ci.org/guilleiguaran/fakeredis)
|
2
2
|
This a fake implementation of redis-rb for machines without Redis or test environments
|
3
3
|
|
4
4
|
|
@@ -65,6 +65,7 @@ Or:
|
|
65
65
|
* [jredville](https://github.com/jredville)
|
66
66
|
* [redsquirrel](https://github.com/redsquirrel)
|
67
67
|
* [dpick](https://github.com/dpick)
|
68
|
+
* [caius](https://github.com/caius)
|
68
69
|
* [Travis-CI](http://travis-ci.org/) (Travis-CI also uses Fakeredis in its tests!!!)
|
69
70
|
|
70
71
|
|
@@ -81,5 +82,5 @@ Or:
|
|
81
82
|
|
82
83
|
## Copyright
|
83
84
|
|
84
|
-
Copyright (c) 2011-
|
85
|
+
Copyright (c) 2011-2013 Guillermo Iguaran. See LICENSE for
|
85
86
|
further details.
|
data/fakeredis.gemspec
CHANGED
@@ -8,12 +8,11 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Guillermo Iguaran"]
|
10
10
|
s.email = ["guilleiguaran@gmail.com"]
|
11
|
-
s.homepage = "https://github.com/
|
11
|
+
s.homepage = "https://guilleiguaran.github.com/fakeredis"
|
12
|
+
s.license = "MIT"
|
12
13
|
s.summary = %q{Fake (In-memory) driver for redis-rb.}
|
13
14
|
s.description = %q{Fake (In-memory) driver for redis-rb. Useful for testing environment and machines without Redis.}
|
14
15
|
|
15
|
-
s.rubyforge_project = "fakeredis"
|
16
|
-
|
17
16
|
s.files = `git ls-files`.split("\n")
|
18
17
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
data/lib/fake_redis.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "fakeredis"
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
# Represents a normal hash with some additional expiration information
|
3
|
+
# associated with each key
|
4
|
+
class ExpiringHash < Hash
|
5
|
+
attr_reader :expires
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
@expires = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
key = normalize key
|
14
|
+
delete(key) if expired?(key)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(key, val)
|
19
|
+
key = normalize key
|
20
|
+
expire(key)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
key = normalize key
|
26
|
+
expire(key)
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def expire(key)
|
31
|
+
key = normalize key
|
32
|
+
expires.delete(key)
|
33
|
+
end
|
34
|
+
|
35
|
+
def expired?(key)
|
36
|
+
key = normalize key
|
37
|
+
expires.include?(key) && expires[key] < Time.now
|
38
|
+
end
|
39
|
+
|
40
|
+
def key?(key)
|
41
|
+
key = normalize key
|
42
|
+
delete(key) if expired?(key)
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def values_at(*keys)
|
47
|
+
keys.each do |key|
|
48
|
+
key = normalize(key)
|
49
|
+
delete(key) if expired?(key)
|
50
|
+
end
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def keys
|
55
|
+
super.select do |key|
|
56
|
+
key = normalize(key)
|
57
|
+
if expired?(key)
|
58
|
+
delete(key)
|
59
|
+
false
|
60
|
+
else
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def normalize key
|
67
|
+
key.to_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/fakeredis/rspec.rb
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
# Takes in the variable length array of arguments for a zinterstore/zunionstore method
|
3
|
+
# and parses them into a few attributes for the method to access.
|
4
|
+
#
|
5
|
+
# Handles throwing errors for various scenarios (matches redis):
|
6
|
+
# * Custom weights specified, but not enough or too many given
|
7
|
+
# * Invalid aggregate value given
|
8
|
+
# * Multiple aggregate values given
|
9
|
+
class SortedSetArgumentHandler
|
10
|
+
# [Symbol] The aggregate method to use for the output values. One of %w(sum min max) expected
|
11
|
+
attr_reader :aggregate
|
12
|
+
# [Integer] Number of keys in the argument list
|
13
|
+
attr_accessor :number_of_keys
|
14
|
+
# [Array] The actual keys in the argument list
|
15
|
+
attr_accessor :keys
|
16
|
+
# [Array] integers for weighting the values of each key - one number per key expected
|
17
|
+
attr_accessor :weights
|
18
|
+
|
19
|
+
# Used internally
|
20
|
+
attr_accessor :type
|
21
|
+
|
22
|
+
# Expects all the argments for the method to be passed as an array
|
23
|
+
def initialize args
|
24
|
+
# Pull out known lengths of data
|
25
|
+
self.number_of_keys = args.shift
|
26
|
+
self.keys = args.shift(number_of_keys)
|
27
|
+
# Handle the variable lengths of data (WEIGHTS/AGGREGATE)
|
28
|
+
args.inject(self) {|handler, item| handler.handle(item) }
|
29
|
+
|
30
|
+
# Defaults for unspecified things
|
31
|
+
self.weights ||= Array.new(number_of_keys) { 1 }
|
32
|
+
self.aggregate ||= :sum
|
33
|
+
|
34
|
+
# Validate values
|
35
|
+
raise(Redis::CommandError, "ERR syntax error") unless weights.size == number_of_keys
|
36
|
+
raise(Redis::CommandError, "ERR syntax error") unless [:min, :max, :sum].include?(aggregate)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Only allows assigning a value *once* - raises Redis::CommandError if a second is given
|
40
|
+
def aggregate=(str)
|
41
|
+
raise(Redis::CommandError, "ERR syntax error") if (defined?(@aggregate) && @aggregate)
|
42
|
+
@aggregate = str.to_s.downcase.to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
# Decides how to handle an item, depending on where we are in the arguments
|
46
|
+
def handle(item)
|
47
|
+
case item
|
48
|
+
when "WEIGHTS"
|
49
|
+
self.type = :weights
|
50
|
+
self.weights = []
|
51
|
+
when "AGGREGATE"
|
52
|
+
self.type = :aggregate
|
53
|
+
when nil
|
54
|
+
# This should never be called, raise a syntax error if we manage to hit it
|
55
|
+
raise(Redis::CommandError, "ERR syntax error")
|
56
|
+
else
|
57
|
+
send "handle_#{type}", item
|
58
|
+
end
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_weights(item)
|
63
|
+
self.weights << item
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_aggregate(item)
|
67
|
+
self.aggregate = item
|
68
|
+
end
|
69
|
+
|
70
|
+
def inject_block
|
71
|
+
lambda { |handler, item| handler.handle(item) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
class SortedSetStore
|
3
|
+
attr_accessor :data, :weights, :aggregate, :keys
|
4
|
+
|
5
|
+
def initialize params, data
|
6
|
+
self.data = data
|
7
|
+
self.weights = params.weights
|
8
|
+
self.aggregate = params.aggregate
|
9
|
+
self.keys = params.keys
|
10
|
+
end
|
11
|
+
|
12
|
+
def hashes
|
13
|
+
@hashes ||= keys.map do |src|
|
14
|
+
case data[src]
|
15
|
+
when ::Set
|
16
|
+
# Every value has a score of 1
|
17
|
+
Hash[data[src].map {|k,v| [k, 1]}]
|
18
|
+
when Hash
|
19
|
+
data[src]
|
20
|
+
else
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Apply the weightings to the hashes
|
27
|
+
def computed_values
|
28
|
+
unless defined?(@computed_values) && @computed_values
|
29
|
+
# Do nothing if all weights are 1, as n * 1 is n
|
30
|
+
@computed_values = hashes if weights.all? {|weight| weight == 1 }
|
31
|
+
# Otherwise, multiply the values in each hash by that hash's weighting
|
32
|
+
@computed_values ||= hashes.each_with_index.map do |hash, index|
|
33
|
+
weight = weights[index]
|
34
|
+
Hash[hash.map {|k, v| [k, (v * weight)]}]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@computed_values
|
38
|
+
end
|
39
|
+
|
40
|
+
def aggregate_sum out
|
41
|
+
selected_keys.each do |key|
|
42
|
+
out[key] = computed_values.inject(0) do |n, hash|
|
43
|
+
n + (hash[key] || 0)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def aggregate_min out
|
49
|
+
selected_keys.each do |key|
|
50
|
+
out[key] = computed_values.map {|h| h[key] }.compact.min
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def aggregate_max out
|
55
|
+
selected_keys.each do |key|
|
56
|
+
out[key] = computed_values.map {|h| h[key] }.compact.max
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def selected_keys
|
61
|
+
raise NotImplemented, "subclass needs to implement #selected_keys"
|
62
|
+
end
|
63
|
+
|
64
|
+
def call
|
65
|
+
ZSet.new.tap {|out| send("aggregate_#{aggregate}", out) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class SortedSetIntersectStore < SortedSetStore
|
70
|
+
def selected_keys
|
71
|
+
@values ||= hashes.inject([]) { |r, h| r.empty? ? h.keys : (r & h.keys) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class SortedSetUnionStore < SortedSetStore
|
76
|
+
def selected_keys
|
77
|
+
@values ||= hashes.map(&:keys).flatten.uniq
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/fakeredis/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
module FakeRedis
|
2
|
+
class ZSet < Hash
|
3
|
+
|
4
|
+
def []=(key, val)
|
5
|
+
super(key, _floatify(val))
|
6
|
+
end
|
7
|
+
|
8
|
+
# Increments the value of key by val
|
9
|
+
def increment(key, val)
|
10
|
+
self[key] += _floatify(val)
|
11
|
+
end
|
12
|
+
|
13
|
+
def select_by_score min, max
|
14
|
+
min = _floatify(min)
|
15
|
+
max = _floatify(max)
|
16
|
+
reject {|_,v| v < min || v > max }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Originally lifted from redis-rb
|
20
|
+
def _floatify(str)
|
21
|
+
if (( inf = str.to_s.match(/^([+-])?inf/i) ))
|
22
|
+
(inf[1] == "-" ? -1.0 : 1.0) / 0.0
|
23
|
+
else
|
24
|
+
Float str
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -1,104 +1,88 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'redis/connection/registry'
|
3
3
|
require 'redis/connection/command_helper'
|
4
|
+
require "fakeredis/expiring_hash"
|
5
|
+
require "fakeredis/sorted_set_argument_handler"
|
6
|
+
require "fakeredis/sorted_set_store"
|
7
|
+
require "fakeredis/zset"
|
4
8
|
|
5
9
|
class Redis
|
6
10
|
module Connection
|
7
11
|
class Memory
|
8
|
-
|
9
|
-
|
10
|
-
class ExpiringHash < Hash
|
11
|
-
attr_reader :expires
|
12
|
-
|
13
|
-
def initialize(*)
|
14
|
-
super
|
15
|
-
@expires = {}
|
16
|
-
end
|
17
|
-
|
18
|
-
def [](key)
|
19
|
-
delete(key) if expired?(key)
|
20
|
-
super
|
21
|
-
end
|
22
|
-
|
23
|
-
def []=(key, val)
|
24
|
-
expire(key)
|
25
|
-
super
|
26
|
-
end
|
12
|
+
include Redis::Connection::CommandHelper
|
13
|
+
include FakeRedis
|
27
14
|
|
28
|
-
|
29
|
-
expire(key)
|
30
|
-
super
|
31
|
-
end
|
15
|
+
attr_accessor :buffer, :options
|
32
16
|
|
33
|
-
|
34
|
-
|
35
|
-
|
17
|
+
# Tracks all databases for all instances across the current process.
|
18
|
+
# We have to be able to handle two clients with the same host/port accessing
|
19
|
+
# different databases at once without overwriting each other. So we store our
|
20
|
+
# "data" outside the client instances, in this class level instance method.
|
21
|
+
# Client instances access it with a key made up of their host/port, and then select
|
22
|
+
# which DB out of the array of them they want. Allows the access we need.
|
23
|
+
def self.databases
|
24
|
+
@databases ||= Hash.new {|h,k| h[k] = [] }
|
25
|
+
end
|
36
26
|
|
37
|
-
|
38
|
-
|
39
|
-
|
27
|
+
# Used for resetting everything in specs
|
28
|
+
def self.reset_all_databases
|
29
|
+
@databases = nil
|
30
|
+
end
|
40
31
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
32
|
+
def self.connect(options = {})
|
33
|
+
new(options)
|
34
|
+
end
|
45
35
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
36
|
+
def initialize(options = {})
|
37
|
+
self.options = options
|
38
|
+
end
|
50
39
|
|
51
|
-
|
52
|
-
|
53
|
-
if expired?(key)
|
54
|
-
delete(key)
|
55
|
-
false
|
56
|
-
else
|
57
|
-
true
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
40
|
+
def database_id
|
41
|
+
@database_id ||= 0
|
61
42
|
end
|
43
|
+
attr_writer :database_id
|
62
44
|
|
63
|
-
|
45
|
+
def database_instance_key
|
46
|
+
[options[:host], options[:port]].hash
|
64
47
|
end
|
65
48
|
|
66
|
-
|
49
|
+
def databases
|
50
|
+
self.class.databases[database_instance_key]
|
51
|
+
end
|
67
52
|
|
68
|
-
def
|
69
|
-
|
70
|
-
@connected = connected
|
71
|
-
@replies = []
|
72
|
-
@buffer = nil
|
53
|
+
def find_database id=database_id
|
54
|
+
databases[id] ||= ExpiringHash.new
|
73
55
|
end
|
74
56
|
|
75
|
-
def
|
76
|
-
|
57
|
+
def data
|
58
|
+
find_database
|
77
59
|
end
|
78
60
|
|
79
|
-
def
|
80
|
-
@
|
61
|
+
def replies
|
62
|
+
@replies ||= []
|
81
63
|
end
|
64
|
+
attr_writer :replies
|
82
65
|
|
83
|
-
def
|
84
|
-
|
66
|
+
def connected?
|
67
|
+
true
|
85
68
|
end
|
86
69
|
|
87
70
|
def connect_unix(path, timeout)
|
88
|
-
@connected = true
|
89
71
|
end
|
90
72
|
|
91
73
|
def disconnect
|
92
|
-
@connected = false
|
93
|
-
nil
|
94
74
|
end
|
95
75
|
|
96
76
|
def timeout=(usecs)
|
97
77
|
end
|
98
78
|
|
99
79
|
def write(command)
|
100
|
-
|
101
|
-
|
80
|
+
meffod = command.shift
|
81
|
+
if respond_to?(meffod)
|
82
|
+
reply = send(meffod, *command)
|
83
|
+
else
|
84
|
+
raise Redis::CommandError, "ERR unknown command '#{meffod}'"
|
85
|
+
end
|
102
86
|
|
103
87
|
if reply == true
|
104
88
|
reply = 1
|
@@ -106,13 +90,13 @@ class Redis
|
|
106
90
|
reply = 0
|
107
91
|
end
|
108
92
|
|
109
|
-
|
110
|
-
|
93
|
+
replies << reply
|
94
|
+
buffer << reply if buffer && meffod != :multi
|
111
95
|
nil
|
112
96
|
end
|
113
97
|
|
114
98
|
def read
|
115
|
-
|
99
|
+
replies.shift
|
116
100
|
end
|
117
101
|
|
118
102
|
# NOT IMPLEMENTED:
|
@@ -120,25 +104,30 @@ class Redis
|
|
120
104
|
# * brpop
|
121
105
|
# * brpoplpush
|
122
106
|
# * discard
|
123
|
-
# *
|
107
|
+
# * sort
|
124
108
|
# * subscribe
|
125
109
|
# * psubscribe
|
126
110
|
# * publish
|
127
|
-
|
128
|
-
# * zunionstore
|
111
|
+
|
129
112
|
def flushdb
|
130
|
-
|
113
|
+
databases.delete_at(database_id)
|
114
|
+
"OK"
|
131
115
|
end
|
132
116
|
|
133
117
|
def flushall
|
134
|
-
|
118
|
+
self.class.databases[database_instance_key] = []
|
119
|
+
"OK"
|
135
120
|
end
|
136
121
|
|
137
122
|
def auth(password)
|
138
123
|
"OK"
|
139
124
|
end
|
140
125
|
|
141
|
-
def select(index)
|
126
|
+
def select(index)
|
127
|
+
data_type_check(index, Integer)
|
128
|
+
self.database_id = index
|
129
|
+
"OK"
|
130
|
+
end
|
142
131
|
|
143
132
|
def info
|
144
133
|
{
|
@@ -163,71 +152,84 @@ class Redis
|
|
163
152
|
|
164
153
|
def bgreriteaof ; end
|
165
154
|
|
155
|
+
def move key, destination_id
|
156
|
+
raise Redis::CommandError, "ERR source and destination objects are the same" if destination_id == database_id
|
157
|
+
destination = find_database(destination_id)
|
158
|
+
return false unless data.has_key?(key)
|
159
|
+
return false if destination.has_key?(key)
|
160
|
+
destination[key] = data.delete(key)
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
166
164
|
def get(key)
|
167
|
-
|
165
|
+
data_type_check(key, String)
|
166
|
+
data[key]
|
168
167
|
end
|
169
168
|
|
170
169
|
def getbit(key, offset)
|
171
|
-
return unless
|
172
|
-
|
170
|
+
return unless data[key]
|
171
|
+
data[key].unpack('B*')[0].split("")[offset].to_i
|
173
172
|
end
|
174
173
|
|
175
174
|
def getrange(key, start, ending)
|
176
|
-
return unless
|
177
|
-
|
175
|
+
return unless data[key]
|
176
|
+
data[key][start..ending]
|
178
177
|
end
|
179
178
|
alias :substr :getrange
|
180
179
|
|
181
180
|
def getset(key, value)
|
182
|
-
|
183
|
-
|
184
|
-
|
181
|
+
data_type_check(key, String)
|
182
|
+
data[key].tap do
|
183
|
+
set(key, value)
|
184
|
+
end
|
185
185
|
end
|
186
186
|
|
187
187
|
def mget(*keys)
|
188
188
|
raise Redis::CommandError, "wrong number of arguments for 'mget' command" if keys.empty?
|
189
|
-
|
189
|
+
# We work with either an array, or list of arguments
|
190
|
+
keys = keys.first if keys.size == 1
|
191
|
+
data.values_at(*keys)
|
190
192
|
end
|
191
193
|
|
192
194
|
def append(key, value)
|
193
|
-
|
194
|
-
|
195
|
+
data[key] = (data[key] || "")
|
196
|
+
data[key] = data[key] + value.to_s
|
195
197
|
end
|
196
198
|
|
197
199
|
def strlen(key)
|
198
|
-
return unless
|
199
|
-
|
200
|
+
return unless data[key]
|
201
|
+
data[key].size
|
200
202
|
end
|
201
203
|
|
202
204
|
def hgetall(key)
|
203
205
|
data_type_check(key, Hash)
|
204
|
-
|
206
|
+
data[key].to_a.flatten || {}
|
205
207
|
end
|
206
208
|
|
207
209
|
def hget(key, field)
|
208
210
|
data_type_check(key, Hash)
|
209
|
-
|
211
|
+
data[key] && data[key][field.to_s]
|
210
212
|
end
|
211
213
|
|
212
214
|
def hdel(key, field)
|
213
215
|
data_type_check(key, Hash)
|
214
|
-
|
216
|
+
data[key] && data[key].delete(field)
|
215
217
|
remove_key_for_empty_collection(key)
|
216
218
|
end
|
217
219
|
|
218
220
|
def hkeys(key)
|
219
221
|
data_type_check(key, Hash)
|
220
|
-
return [] if
|
221
|
-
|
222
|
+
return [] if data[key].nil?
|
223
|
+
data[key].keys
|
222
224
|
end
|
223
225
|
|
224
226
|
def keys(pattern = "*")
|
225
227
|
regexp = Regexp.new(pattern.split("*").map { |r| Regexp.escape(r) }.join(".*"))
|
226
|
-
|
228
|
+
data.keys.select { |key| key =~ regexp }
|
227
229
|
end
|
228
230
|
|
229
231
|
def randomkey
|
230
|
-
|
232
|
+
data.keys[rand(dbsize)]
|
231
233
|
end
|
232
234
|
|
233
235
|
def echo(string)
|
@@ -243,66 +245,66 @@ class Redis
|
|
243
245
|
end
|
244
246
|
|
245
247
|
def dbsize
|
246
|
-
|
248
|
+
data.keys.count
|
247
249
|
end
|
248
250
|
|
249
251
|
def exists(key)
|
250
|
-
|
252
|
+
data.key?(key)
|
251
253
|
end
|
252
254
|
|
253
255
|
def llen(key)
|
254
256
|
data_type_check(key, Array)
|
255
|
-
return 0 unless
|
256
|
-
|
257
|
+
return 0 unless data[key]
|
258
|
+
data[key].size
|
257
259
|
end
|
258
260
|
|
259
261
|
def lrange(key, startidx, endidx)
|
260
262
|
data_type_check(key, Array)
|
261
|
-
(
|
263
|
+
(data[key] && data[key][startidx..endidx]) || []
|
262
264
|
end
|
263
265
|
|
264
266
|
def ltrim(key, start, stop)
|
265
267
|
data_type_check(key, Array)
|
266
|
-
return unless
|
267
|
-
|
268
|
+
return unless data[key]
|
269
|
+
data[key] = data[key][start..stop]
|
268
270
|
end
|
269
271
|
|
270
272
|
def lindex(key, index)
|
271
273
|
data_type_check(key, Array)
|
272
|
-
|
274
|
+
data[key] && data[key][index]
|
273
275
|
end
|
274
276
|
|
275
277
|
def linsert(key, where, pivot, value)
|
276
278
|
data_type_check(key, Array)
|
277
|
-
return unless
|
278
|
-
index =
|
279
|
+
return unless data[key]
|
280
|
+
index = data[key].index(pivot)
|
279
281
|
case where
|
280
|
-
when :before then
|
281
|
-
when :after then
|
282
|
+
when :before then data[key].insert(index, value)
|
283
|
+
when :after then data[key].insert(index + 1, value)
|
282
284
|
else raise Redis::CommandError, "ERR syntax error"
|
283
285
|
end
|
284
286
|
end
|
285
287
|
|
286
288
|
def lset(key, index, value)
|
287
289
|
data_type_check(key, Array)
|
288
|
-
return unless
|
289
|
-
raise
|
290
|
-
|
290
|
+
return unless data[key]
|
291
|
+
raise(Redis::CommandError, "ERR index out of range") if index >= data[key].size
|
292
|
+
data[key][index] = value
|
291
293
|
end
|
292
294
|
|
293
295
|
def lrem(key, count, value)
|
294
296
|
data_type_check(key, Array)
|
295
|
-
return unless
|
296
|
-
old_size =
|
297
|
+
return unless data[key]
|
298
|
+
old_size = data[key].size
|
297
299
|
diff =
|
298
300
|
if count == 0
|
299
|
-
|
300
|
-
old_size -
|
301
|
+
data[key].delete(value)
|
302
|
+
old_size - data[key].size
|
301
303
|
else
|
302
|
-
array = count > 0 ?
|
304
|
+
array = count > 0 ? data[key].dup : data[key].reverse
|
303
305
|
count.abs.times{ array.delete_at(array.index(value) || array.length) }
|
304
|
-
|
305
|
-
old_size -
|
306
|
+
data[key] = count > 0 ? array.dup : array.reverse
|
307
|
+
old_size - data[key].size
|
306
308
|
end
|
307
309
|
remove_key_for_empty_collection(key)
|
308
310
|
diff
|
@@ -310,75 +312,76 @@ class Redis
|
|
310
312
|
|
311
313
|
def rpush(key, value)
|
312
314
|
data_type_check(key, Array)
|
313
|
-
|
315
|
+
data[key] ||= []
|
314
316
|
[value].flatten.each do |val|
|
315
|
-
|
317
|
+
data[key].push(val.to_s)
|
316
318
|
end
|
317
|
-
|
319
|
+
data[key].size
|
318
320
|
end
|
319
321
|
|
320
322
|
def rpushx(key, value)
|
321
323
|
data_type_check(key, Array)
|
322
|
-
return unless
|
324
|
+
return unless data[key]
|
323
325
|
rpush(key, value)
|
324
326
|
end
|
325
327
|
|
326
328
|
def lpush(key, value)
|
327
329
|
data_type_check(key, Array)
|
328
|
-
|
330
|
+
data[key] ||= []
|
329
331
|
[value].flatten.each do |val|
|
330
|
-
|
332
|
+
data[key].unshift(val.to_s)
|
331
333
|
end
|
332
|
-
|
334
|
+
data[key].size
|
333
335
|
end
|
334
336
|
|
335
337
|
def lpushx(key, value)
|
336
338
|
data_type_check(key, Array)
|
337
|
-
return unless
|
339
|
+
return unless data[key]
|
338
340
|
lpush(key, value)
|
339
341
|
end
|
340
342
|
|
341
343
|
def rpop(key)
|
342
344
|
data_type_check(key, Array)
|
343
|
-
return unless
|
344
|
-
|
345
|
+
return unless data[key]
|
346
|
+
data[key].pop
|
345
347
|
end
|
346
348
|
|
347
349
|
def rpoplpush(key1, key2)
|
348
350
|
data_type_check(key1, Array)
|
349
|
-
|
350
|
-
|
351
|
+
rpop(key1).tap do |elem|
|
352
|
+
lpush(key2, elem)
|
353
|
+
end
|
351
354
|
end
|
352
355
|
|
353
356
|
def lpop(key)
|
354
357
|
data_type_check(key, Array)
|
355
|
-
return unless
|
356
|
-
|
358
|
+
return unless data[key]
|
359
|
+
data[key].shift
|
357
360
|
end
|
358
361
|
|
359
362
|
def smembers(key)
|
360
363
|
data_type_check(key, ::Set)
|
361
|
-
return [] unless
|
362
|
-
|
364
|
+
return [] unless data[key]
|
365
|
+
data[key].to_a.reverse
|
363
366
|
end
|
364
367
|
|
365
368
|
def sismember(key, value)
|
366
369
|
data_type_check(key, ::Set)
|
367
|
-
return false unless
|
368
|
-
|
370
|
+
return false unless data[key]
|
371
|
+
data[key].include?(value.to_s)
|
369
372
|
end
|
370
373
|
|
371
374
|
def sadd(key, value)
|
372
375
|
data_type_check(key, ::Set)
|
373
376
|
value = Array(value)
|
374
377
|
|
375
|
-
result = if
|
376
|
-
old_set =
|
377
|
-
|
378
|
-
(
|
378
|
+
result = if data[key]
|
379
|
+
old_set = data[key].dup
|
380
|
+
data[key].merge(value.map(&:to_s))
|
381
|
+
(data[key] - old_set).size
|
379
382
|
else
|
380
|
-
|
381
|
-
|
383
|
+
data[key] = ::Set.new(value.map(&:to_s))
|
384
|
+
data[key].size
|
382
385
|
end
|
383
386
|
|
384
387
|
# 0 = false, 1 = true, 2+ untouched
|
@@ -388,7 +391,7 @@ class Redis
|
|
388
391
|
|
389
392
|
def srem(key, value)
|
390
393
|
data_type_check(key, ::Set)
|
391
|
-
deleted = !!(
|
394
|
+
deleted = !!(data[key] && data[key].delete?(value.to_s))
|
392
395
|
remove_key_for_empty_collection(key)
|
393
396
|
deleted
|
394
397
|
end
|
@@ -409,14 +412,14 @@ class Redis
|
|
409
412
|
|
410
413
|
def scard(key)
|
411
414
|
data_type_check(key, ::Set)
|
412
|
-
return 0 unless
|
413
|
-
|
415
|
+
return 0 unless data[key]
|
416
|
+
data[key].size
|
414
417
|
end
|
415
418
|
|
416
419
|
def sinter(*keys)
|
417
420
|
keys.each { |k| data_type_check(k, ::Set) }
|
418
|
-
return ::Set.new if keys.any? { |k|
|
419
|
-
keys = keys.map { |k|
|
421
|
+
return ::Set.new if keys.any? { |k| data[k].nil? }
|
422
|
+
keys = keys.map { |k| data[k] || ::Set.new }
|
420
423
|
keys.inject do |set, key|
|
421
424
|
set & key
|
422
425
|
end.to_a
|
@@ -425,12 +428,12 @@ class Redis
|
|
425
428
|
def sinterstore(destination, *keys)
|
426
429
|
data_type_check(destination, ::Set)
|
427
430
|
result = sinter(*keys)
|
428
|
-
|
431
|
+
data[destination] = ::Set.new(result)
|
429
432
|
end
|
430
433
|
|
431
434
|
def sunion(*keys)
|
432
435
|
keys.each { |k| data_type_check(k, ::Set) }
|
433
|
-
keys = keys.map { |k|
|
436
|
+
keys = keys.map { |k| data[k] || ::Set.new }
|
434
437
|
keys.inject(::Set.new) do |set, key|
|
435
438
|
set | key
|
436
439
|
end.to_a
|
@@ -439,13 +442,13 @@ class Redis
|
|
439
442
|
def sunionstore(destination, *keys)
|
440
443
|
data_type_check(destination, ::Set)
|
441
444
|
result = sunion(*keys)
|
442
|
-
|
445
|
+
data[destination] = ::Set.new(result)
|
443
446
|
end
|
444
447
|
|
445
448
|
def sdiff(key1, *keys)
|
446
449
|
[key1, *keys].each { |k| data_type_check(k, ::Set) }
|
447
|
-
keys = keys.map { |k|
|
448
|
-
keys.inject(
|
450
|
+
keys = keys.map { |k| data[k] || ::Set.new }
|
451
|
+
keys.inject(data[key1]) do |memo, set|
|
449
452
|
memo - set
|
450
453
|
end.to_a
|
451
454
|
end
|
@@ -453,23 +456,23 @@ class Redis
|
|
453
456
|
def sdiffstore(destination, key1, *keys)
|
454
457
|
data_type_check(destination, ::Set)
|
455
458
|
result = sdiff(key1, *keys)
|
456
|
-
|
459
|
+
data[destination] = ::Set.new(result)
|
457
460
|
end
|
458
461
|
|
459
462
|
def srandmember(key)
|
460
463
|
data_type_check(key, ::Set)
|
461
|
-
return nil unless
|
462
|
-
|
464
|
+
return nil unless data[key]
|
465
|
+
data[key].to_a[rand(data[key].size)]
|
463
466
|
end
|
464
467
|
|
465
468
|
def del(*keys)
|
466
469
|
keys = keys.flatten(1)
|
467
470
|
raise Redis::CommandError, "ERR wrong number of arguments for 'del' command" if keys.empty?
|
468
|
-
old_count =
|
471
|
+
old_count = data.keys.size
|
469
472
|
keys.each do |key|
|
470
|
-
|
473
|
+
data.delete(key)
|
471
474
|
end
|
472
|
-
|
475
|
+
old_count - data.keys.size
|
473
476
|
end
|
474
477
|
|
475
478
|
def setnx(key, value)
|
@@ -482,10 +485,10 @@ class Redis
|
|
482
485
|
end
|
483
486
|
|
484
487
|
def rename(key, new_key)
|
485
|
-
return unless
|
486
|
-
|
487
|
-
|
488
|
-
|
488
|
+
return unless data[key]
|
489
|
+
data[new_key] = data[key]
|
490
|
+
data.expires[new_key] = data.expires[key] if data.expires.include?(key)
|
491
|
+
data.delete(key)
|
489
492
|
end
|
490
493
|
|
491
494
|
def renamenx(key, new_key)
|
@@ -498,13 +501,13 @@ class Redis
|
|
498
501
|
end
|
499
502
|
|
500
503
|
def expire(key, ttl)
|
501
|
-
return unless
|
502
|
-
|
504
|
+
return unless data[key]
|
505
|
+
data.expires[key] = Time.now + ttl
|
503
506
|
true
|
504
507
|
end
|
505
508
|
|
506
509
|
def ttl(key)
|
507
|
-
if
|
510
|
+
if data.expires.include?(key) && (ttl = data.expires[key].to_i - Time.now.to_i) > 0
|
508
511
|
ttl
|
509
512
|
else
|
510
513
|
-1
|
@@ -512,23 +515,23 @@ class Redis
|
|
512
515
|
end
|
513
516
|
|
514
517
|
def expireat(key, timestamp)
|
515
|
-
|
518
|
+
data.expires[key] = Time.at(timestamp)
|
516
519
|
true
|
517
520
|
end
|
518
521
|
|
519
522
|
def persist(key)
|
520
|
-
|
523
|
+
!!data.expires.delete(key)
|
521
524
|
end
|
522
525
|
|
523
526
|
def hset(key, field, value)
|
524
527
|
data_type_check(key, Hash)
|
525
528
|
field = field.to_s
|
526
|
-
if
|
527
|
-
result =
|
528
|
-
|
529
|
+
if data[key]
|
530
|
+
result = !data[key].include?(field)
|
531
|
+
data[key][field] = value.to_s
|
529
532
|
result
|
530
533
|
else
|
531
|
-
|
534
|
+
data[key] = { field => value.to_s }
|
532
535
|
true
|
533
536
|
end
|
534
537
|
end
|
@@ -536,27 +539,28 @@ class Redis
|
|
536
539
|
def hsetnx(key, field, value)
|
537
540
|
data_type_check(key, Hash)
|
538
541
|
field = field.to_s
|
539
|
-
return false if
|
542
|
+
return false if data[key] && data[key][field]
|
540
543
|
hset(key, field, value)
|
541
544
|
end
|
542
545
|
|
543
546
|
def hmset(key, *fields)
|
544
|
-
|
547
|
+
# mapped_hmset gives us [[:k1, "v1", :k2, "v2"]] for `fields`. Fix that.
|
548
|
+
fields = fields[0] if mapped_param?(fields)
|
549
|
+
raise Redis::CommandError, "ERR wrong number of arguments for HMSET" if fields.empty? || fields.size.odd?
|
545
550
|
data_type_check(key, Hash)
|
546
|
-
|
551
|
+
data[key] ||= {}
|
547
552
|
fields.each_slice(2) do |field|
|
548
|
-
|
553
|
+
data[key][field[0].to_s] = field[1].to_s
|
549
554
|
end
|
550
555
|
end
|
551
556
|
|
552
557
|
def hmget(key, *fields)
|
553
558
|
raise Redis::CommandError, "wrong number of arguments for 'hmget' command" if fields.empty?
|
554
559
|
data_type_check(key, Hash)
|
555
|
-
values = []
|
556
560
|
fields.map do |field|
|
557
561
|
field = field.to_s
|
558
|
-
if
|
559
|
-
|
562
|
+
if data[key]
|
563
|
+
data[key][field]
|
560
564
|
else
|
561
565
|
nil
|
562
566
|
end
|
@@ -565,30 +569,30 @@ class Redis
|
|
565
569
|
|
566
570
|
def hlen(key)
|
567
571
|
data_type_check(key, Hash)
|
568
|
-
return 0 unless
|
569
|
-
|
572
|
+
return 0 unless data[key]
|
573
|
+
data[key].size
|
570
574
|
end
|
571
575
|
|
572
576
|
def hvals(key)
|
573
577
|
data_type_check(key, Hash)
|
574
|
-
return [] unless
|
575
|
-
|
578
|
+
return [] unless data[key]
|
579
|
+
data[key].values
|
576
580
|
end
|
577
581
|
|
578
582
|
def hincrby(key, field, increment)
|
579
583
|
data_type_check(key, Hash)
|
580
|
-
if
|
581
|
-
|
584
|
+
if data[key]
|
585
|
+
data[key][field] = (data[key][field.to_s].to_i + increment.to_i).to_s
|
582
586
|
else
|
583
|
-
|
587
|
+
data[key] = { field => increment.to_s }
|
584
588
|
end
|
585
|
-
|
589
|
+
data[key][field].to_i
|
586
590
|
end
|
587
591
|
|
588
592
|
def hexists(key, field)
|
589
593
|
data_type_check(key, Hash)
|
590
|
-
return false unless
|
591
|
-
|
594
|
+
return false unless data[key]
|
595
|
+
data[key].key?(field)
|
592
596
|
end
|
593
597
|
|
594
598
|
def sync ; end
|
@@ -602,44 +606,48 @@ class Redis
|
|
602
606
|
end
|
603
607
|
|
604
608
|
def set(key, value)
|
605
|
-
|
609
|
+
data[key] = value.to_s
|
606
610
|
"OK"
|
607
611
|
end
|
608
612
|
|
609
613
|
def setbit(key, offset, bit)
|
610
|
-
old_val =
|
614
|
+
old_val = data[key] ? data[key].unpack('B*')[0].split("") : []
|
611
615
|
size_increment = [((offset/8)+1)*8-old_val.length, 0].max
|
612
616
|
old_val += Array.new(size_increment).map{"0"}
|
613
617
|
original_val = old_val[offset]
|
614
618
|
old_val[offset] = bit.to_s
|
615
619
|
new_val = ""
|
616
620
|
old_val.each_slice(8){|b| new_val = new_val + b.join("").to_i(2).chr }
|
617
|
-
|
621
|
+
data[key] = new_val
|
618
622
|
original_val
|
619
623
|
end
|
620
624
|
|
621
625
|
def setex(key, seconds, value)
|
622
|
-
|
626
|
+
data[key] = value.to_s
|
623
627
|
expire(key, seconds)
|
624
628
|
end
|
625
629
|
|
626
630
|
def setrange(key, offset, value)
|
627
|
-
return unless
|
628
|
-
s =
|
629
|
-
|
631
|
+
return unless data[key]
|
632
|
+
s = data[key][offset,value.size]
|
633
|
+
data[key][s] = value
|
630
634
|
end
|
631
635
|
|
632
636
|
def mset(*pairs)
|
637
|
+
# Handle pairs for mapped_mset command
|
638
|
+
pairs = pairs[0] if mapped_param?(pairs)
|
633
639
|
pairs.each_slice(2) do |pair|
|
634
|
-
|
640
|
+
data[pair[0].to_s] = pair[1].to_s
|
635
641
|
end
|
636
642
|
"OK"
|
637
643
|
end
|
638
644
|
|
639
645
|
def msetnx(*pairs)
|
646
|
+
# Handle pairs for mapped_msetnx command
|
647
|
+
pairs = pairs[0] if mapped_param?(pairs)
|
640
648
|
keys = []
|
641
649
|
pairs.each_with_index{|item, index| keys << item.to_s if index % 2 == 0}
|
642
|
-
return if keys.any?{|key|
|
650
|
+
return false if keys.any?{|key| data.key?(key) }
|
643
651
|
mset(*pairs)
|
644
652
|
true
|
645
653
|
end
|
@@ -649,27 +657,27 @@ class Redis
|
|
649
657
|
end
|
650
658
|
|
651
659
|
def incr(key)
|
652
|
-
|
653
|
-
|
660
|
+
data.merge!({ key => (data[key].to_i + 1).to_s || "1"})
|
661
|
+
data[key].to_i
|
654
662
|
end
|
655
663
|
|
656
664
|
def incrby(key, by)
|
657
|
-
|
658
|
-
|
665
|
+
data.merge!({ key => (data[key].to_i + by.to_i).to_s || by })
|
666
|
+
data[key].to_i
|
659
667
|
end
|
660
668
|
|
661
669
|
def decr(key)
|
662
|
-
|
663
|
-
|
670
|
+
data.merge!({ key => (data[key].to_i - 1).to_s || "-1"})
|
671
|
+
data[key].to_i
|
664
672
|
end
|
665
673
|
|
666
674
|
def decrby(key, by)
|
667
|
-
|
668
|
-
|
675
|
+
data.merge!({ key => ((data[key].to_i - by.to_i) || (by.to_i * -1)).to_s })
|
676
|
+
data[key].to_i
|
669
677
|
end
|
670
678
|
|
671
679
|
def type(key)
|
672
|
-
case
|
680
|
+
case data[key]
|
673
681
|
when nil then "none"
|
674
682
|
when String then "string"
|
675
683
|
when Hash then "hash"
|
@@ -685,13 +693,11 @@ class Redis
|
|
685
693
|
def slaveof(host, port) ; end
|
686
694
|
|
687
695
|
def exec
|
688
|
-
buffer =
|
689
|
-
@buffer = nil
|
690
|
-
buffer
|
696
|
+
buffer.tap {|x| self.buffer = nil }
|
691
697
|
end
|
692
698
|
|
693
699
|
def multi
|
694
|
-
|
700
|
+
self.buffer = []
|
695
701
|
yield if block_given?
|
696
702
|
"OK"
|
697
703
|
end
|
@@ -718,17 +724,17 @@ class Redis
|
|
718
724
|
end
|
719
725
|
|
720
726
|
data_type_check(key, ZSet)
|
721
|
-
|
727
|
+
data[key] ||= ZSet.new
|
722
728
|
|
723
729
|
if args.size == 2
|
724
730
|
score, value = args
|
725
|
-
exists =
|
726
|
-
|
731
|
+
exists = !data[key].key?(value.to_s)
|
732
|
+
data[key][value.to_s] = score
|
727
733
|
else
|
728
734
|
# Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already
|
729
735
|
args = args.each_slice(2).to_a unless args.first.is_a?(Array)
|
730
|
-
exists = args.map(&:last).map { |el|
|
731
|
-
args.each { |
|
736
|
+
exists = args.map(&:last).map { |el| data[key].key?(el.to_s) }.count(false)
|
737
|
+
args.each { |s, v| data[key][v.to_s] = s }
|
732
738
|
end
|
733
739
|
|
734
740
|
exists
|
@@ -736,73 +742,85 @@ class Redis
|
|
736
742
|
|
737
743
|
def zrem(key, value)
|
738
744
|
data_type_check(key, ZSet)
|
739
|
-
|
740
|
-
|
745
|
+
values = Array(value)
|
746
|
+
return 0 unless data[key]
|
747
|
+
|
748
|
+
response = values.map do |v|
|
749
|
+
data[key].delete(v) if data[key].has_key?(v)
|
750
|
+
end.compact.size
|
751
|
+
|
741
752
|
remove_key_for_empty_collection(key)
|
742
|
-
|
753
|
+
response
|
743
754
|
end
|
744
755
|
|
745
756
|
def zcard(key)
|
746
757
|
data_type_check(key, ZSet)
|
747
|
-
|
758
|
+
data[key] ? data[key].size : 0
|
748
759
|
end
|
749
760
|
|
750
761
|
def zscore(key, value)
|
751
762
|
data_type_check(key, ZSet)
|
752
|
-
|
763
|
+
value = data[key] && data[key][value.to_s]
|
764
|
+
value && value.to_s
|
753
765
|
end
|
754
766
|
|
755
767
|
def zcount(key, min, max)
|
756
768
|
data_type_check(key, ZSet)
|
757
|
-
return 0 unless
|
758
|
-
|
769
|
+
return 0 unless data[key]
|
770
|
+
data[key].select_by_score(min, max).size
|
759
771
|
end
|
760
772
|
|
761
773
|
def zincrby(key, num, value)
|
762
774
|
data_type_check(key, ZSet)
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
775
|
+
data[key] ||= ZSet.new
|
776
|
+
data[key][value.to_s] ||= 0
|
777
|
+
data[key].increment(value.to_s, num)
|
778
|
+
data[key][value.to_s].to_s
|
767
779
|
end
|
768
780
|
|
769
781
|
def zrank(key, value)
|
770
782
|
data_type_check(key, ZSet)
|
771
|
-
|
783
|
+
data[key].keys.sort_by {|k| data[key][k] }.index(value.to_s)
|
772
784
|
end
|
773
785
|
|
774
786
|
def zrevrank(key, value)
|
775
787
|
data_type_check(key, ZSet)
|
776
|
-
|
788
|
+
data[key].keys.sort_by {|k| -data[key][k] }.index(value.to_s)
|
777
789
|
end
|
778
790
|
|
779
791
|
def zrange(key, start, stop, with_scores = nil)
|
780
792
|
data_type_check(key, ZSet)
|
781
|
-
return [] unless
|
793
|
+
return [] unless data[key]
|
782
794
|
|
783
|
-
if
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
795
|
+
# Sort by score, or if scores are equal, key alphanum
|
796
|
+
results = data[key].sort do |(k1, v1), (k2, v2)|
|
797
|
+
if v1 == v2
|
798
|
+
k1 <=> k2
|
799
|
+
else
|
800
|
+
v1 <=> v2
|
801
|
+
end
|
802
|
+
end
|
803
|
+
# Select just the keys unless we want scores
|
804
|
+
results = results.map(&:first) unless with_scores
|
805
|
+
results[start..stop].flatten.map(&:to_s)
|
788
806
|
end
|
789
807
|
|
790
808
|
def zrevrange(key, start, stop, with_scores = nil)
|
791
809
|
data_type_check(key, ZSet)
|
792
|
-
return [] unless
|
810
|
+
return [] unless data[key]
|
793
811
|
|
794
812
|
if with_scores
|
795
|
-
|
813
|
+
data[key].sort_by {|_,v| -v }
|
796
814
|
else
|
797
|
-
|
815
|
+
data[key].keys.sort_by {|k| -data[key][k] }
|
798
816
|
end[start..stop].flatten.map(&:to_s)
|
799
817
|
end
|
800
818
|
|
801
819
|
def zrangebyscore(key, min, max, *opts)
|
802
820
|
data_type_check(key, ZSet)
|
803
|
-
return [] unless
|
821
|
+
return [] unless data[key]
|
804
822
|
|
805
|
-
range =
|
823
|
+
range = data[key].select_by_score(min, max)
|
806
824
|
vals = if opts.include?('WITHSCORES')
|
807
825
|
range.sort_by {|_,v| v }
|
808
826
|
else
|
@@ -817,9 +835,9 @@ class Redis
|
|
817
835
|
|
818
836
|
def zrevrangebyscore(key, max, min, *opts)
|
819
837
|
data_type_check(key, ZSet)
|
820
|
-
return [] unless
|
838
|
+
return [] unless data[key]
|
821
839
|
|
822
|
-
range =
|
840
|
+
range = data[key].select_by_score(min, max)
|
823
841
|
vals = if opts.include?('WITHSCORES')
|
824
842
|
range.sort_by {|_,v| -v }
|
825
843
|
else
|
@@ -834,66 +852,45 @@ class Redis
|
|
834
852
|
|
835
853
|
def zremrangebyscore(key, min, max)
|
836
854
|
data_type_check(key, ZSet)
|
837
|
-
return 0 unless
|
855
|
+
return 0 unless data[key]
|
838
856
|
|
839
|
-
range =
|
840
|
-
range.each {|k,_|
|
857
|
+
range = data[key].select_by_score(min, max)
|
858
|
+
range.each {|k,_| data[key].delete(k) }
|
841
859
|
range.size
|
842
860
|
end
|
843
861
|
|
844
|
-
def zinterstore(out,
|
862
|
+
def zinterstore(out, *args)
|
845
863
|
data_type_check(out, ZSet)
|
864
|
+
args_handler = SortedSetArgumentHandler.new(args)
|
865
|
+
data[out] = SortedSetIntersectStore.new(args_handler, data).call
|
866
|
+
data[out].size
|
867
|
+
end
|
846
868
|
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
when Hash
|
853
|
-
@data[src]
|
854
|
-
else
|
855
|
-
{}
|
856
|
-
end
|
857
|
-
end
|
858
|
-
|
859
|
-
@data[out] = ZSet.new
|
860
|
-
values = hashes.inject([]) {|r, h| r.empty? ? h.keys : r & h.keys }
|
861
|
-
values.each do |value|
|
862
|
-
@data[out][value] = hashes.inject(0) {|n, h| n + h[value].to_i }
|
863
|
-
end
|
864
|
-
|
865
|
-
@data[out].size
|
869
|
+
def zunionstore(out, *args)
|
870
|
+
data_type_check(out, ZSet)
|
871
|
+
args_handler = SortedSetArgumentHandler.new(args)
|
872
|
+
data[out] = SortedSetUnionStore.new(args_handler, data).call
|
873
|
+
data[out].size
|
866
874
|
end
|
867
875
|
|
868
876
|
def zremrangebyrank(key, start, stop)
|
869
|
-
sorted_elements =
|
877
|
+
sorted_elements = data[key].sort_by { |k, v| v }
|
870
878
|
start = sorted_elements.length if start > sorted_elements.length
|
871
879
|
elements_to_delete = sorted_elements[start..stop]
|
872
|
-
elements_to_delete.each { |elem, rank|
|
880
|
+
elements_to_delete.each { |elem, rank| data[key].delete(elem) }
|
873
881
|
elements_to_delete.size
|
874
882
|
end
|
875
883
|
|
876
884
|
private
|
877
885
|
|
878
|
-
def zrange_select_by_score(key, min, max)
|
879
|
-
if min == '-inf' && max == '+inf'
|
880
|
-
@data[key]
|
881
|
-
elsif max == '+inf'
|
882
|
-
@data[key].reject { |_,v| v < min }
|
883
|
-
elsif min == '-inf'
|
884
|
-
@data[key].reject { |_,v| v > max }
|
885
|
-
else
|
886
|
-
@data[key].reject {|_,v| v < min || v > max }
|
887
|
-
end
|
888
|
-
end
|
889
|
-
|
890
886
|
def remove_key_for_empty_collection(key)
|
891
|
-
del(key) if
|
887
|
+
del(key) if data[key] && data[key].empty?
|
892
888
|
end
|
893
889
|
|
894
890
|
def data_type_check(key, klass)
|
895
|
-
if
|
896
|
-
|
891
|
+
if data[key] && !data[key].is_a?(klass)
|
892
|
+
warn "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}."
|
893
|
+
raise Redis::CommandError.new("ERR Operation against a key holding the wrong kind of value")
|
897
894
|
end
|
898
895
|
end
|
899
896
|
|
@@ -909,6 +906,10 @@ class Redis
|
|
909
906
|
[offset, count]
|
910
907
|
end
|
911
908
|
end
|
909
|
+
|
910
|
+
def mapped_param? param
|
911
|
+
param.size == 1 && param[0].is_a?(Array)
|
912
|
+
end
|
912
913
|
end
|
913
914
|
end
|
914
915
|
end
|