fakeredis 0.3.2 → 0.3.3
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/README.md +16 -2
- data/fakeredis.gemspec +2 -3
- data/lib/fake_redis.rb +1 -0
- data/lib/fakeredis/expiring_hash.rb +56 -0
- 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 +4 -0
- data/lib/redis/connection/memory.rb +82 -114
- data/spec/compatibility_spec.rb +2 -2
- data/spec/connection_spec.rb +13 -3
- data/spec/hashes_spec.rb +44 -33
- data/spec/keys_spec.rb +40 -27
- data/spec/lists_spec.rb +42 -27
- data/spec/server_spec.rb +39 -2
- data/spec/sets_spec.rb +22 -16
- data/spec/sorted_sets_spec.rb +229 -58
- data/spec/spec_helper.rb +4 -0
- data/spec/spec_helper_live_redis.rb +14 -0
- data/spec/strings_spec.rb +92 -45
- metadata +14 -7
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
|
|
@@ -13,6 +13,18 @@ Add it to your Gemfile:
|
|
13
13
|
gem "fakeredis"
|
14
14
|
|
15
15
|
|
16
|
+
## Versions
|
17
|
+
|
18
|
+
FakeRedis currently supports redis-rb v3.0.0 or later, if you are using
|
19
|
+
redis-rb v2.2.x install the version 0.3.x:
|
20
|
+
|
21
|
+
gem install fakeredis -v "~> 0.3.0"
|
22
|
+
|
23
|
+
or use the branch 0-3-x on your Gemfile:
|
24
|
+
|
25
|
+
gem "fakeredis", :git => "git://github.com/guilleiguaran/fakeredis.git", :branch => "0-3-x"
|
26
|
+
|
27
|
+
|
16
28
|
## Usage
|
17
29
|
|
18
30
|
You can use FakeRedis without any changes:
|
@@ -38,7 +50,7 @@ Require this either in your Gemfile or in RSpec's support scripts. So either:
|
|
38
50
|
group :test do
|
39
51
|
gem "rspec"
|
40
52
|
gem "fakeredis", :require => "fakeredis/rspec"
|
41
|
-
end
|
53
|
+
end
|
42
54
|
|
43
55
|
Or:
|
44
56
|
|
@@ -52,6 +64,8 @@ Or:
|
|
52
64
|
* [obrie](https://github.com/obrie)
|
53
65
|
* [jredville](https://github.com/jredville)
|
54
66
|
* [redsquirrel](https://github.com/redsquirrel)
|
67
|
+
* [dpick](https://github.com/dpick)
|
68
|
+
* [caius](https://github.com/caius)
|
55
69
|
* [Travis-CI](http://travis-ci.org/) (Travis-CI also uses Fakeredis in its tests!!!)
|
56
70
|
|
57
71
|
|
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,56 @@
|
|
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
|
+
delete(key) if expired?(key)
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, val)
|
18
|
+
expire(key)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete(key)
|
23
|
+
expire(key)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def expire(key)
|
28
|
+
expires.delete(key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def expired?(key)
|
32
|
+
expires.include?(key) && expires[key] < Time.now
|
33
|
+
end
|
34
|
+
|
35
|
+
def key?(key)
|
36
|
+
delete(key) if expired?(key)
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def values_at(*keys)
|
41
|
+
keys.each {|key| delete(key) if expired?(key)}
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def keys
|
46
|
+
super.select do |key|
|
47
|
+
if expired?(key)
|
48
|
+
delete(key)
|
49
|
+
false
|
50
|
+
else
|
51
|
+
true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -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(RuntimeError, "ERR syntax error") unless weights.size == number_of_keys
|
36
|
+
raise(RuntimeError, "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(RuntimeError, "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(RuntimeError, "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
@@ -1,69 +1,16 @@
|
|
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
|
-
# Represents a normal hash with some additional expiration information
|
9
|
-
# associated with each key
|
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
|
27
|
-
|
28
|
-
def delete(key)
|
29
|
-
expire(key)
|
30
|
-
super
|
31
|
-
end
|
32
|
-
|
33
|
-
def expire(key)
|
34
|
-
expires.delete(key)
|
35
|
-
end
|
36
|
-
|
37
|
-
def expired?(key)
|
38
|
-
expires.include?(key) && expires[key] < Time.now
|
39
|
-
end
|
40
|
-
|
41
|
-
def key?(key)
|
42
|
-
delete(key) if expired?(key)
|
43
|
-
super
|
44
|
-
end
|
45
|
-
|
46
|
-
def values_at(*keys)
|
47
|
-
keys.each {|key| delete(key) if expired?(key)}
|
48
|
-
super
|
49
|
-
end
|
50
|
-
|
51
|
-
def keys
|
52
|
-
super.select do |key|
|
53
|
-
if expired?(key)
|
54
|
-
delete(key)
|
55
|
-
false
|
56
|
-
else
|
57
|
-
true
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
class ZSet < Hash
|
64
|
-
end
|
65
|
-
|
66
12
|
include Redis::Connection::CommandHelper
|
13
|
+
include FakeRedis
|
67
14
|
|
68
15
|
def initialize
|
69
16
|
@data = ExpiringHash.new
|
@@ -93,8 +40,12 @@ class Redis
|
|
93
40
|
end
|
94
41
|
|
95
42
|
def write(command)
|
96
|
-
|
97
|
-
|
43
|
+
meffod = command.shift
|
44
|
+
if respond_to?(meffod)
|
45
|
+
reply = send(meffod, *command)
|
46
|
+
else
|
47
|
+
raise RuntimeError, "ERR unknown command '#{meffod}'"
|
48
|
+
end
|
98
49
|
|
99
50
|
if reply == true
|
100
51
|
reply = 1
|
@@ -103,7 +54,7 @@ class Redis
|
|
103
54
|
end
|
104
55
|
|
105
56
|
@replies << reply
|
106
|
-
@buffer << reply if @buffer &&
|
57
|
+
@buffer << reply if @buffer && meffod != :multi
|
107
58
|
nil
|
108
59
|
end
|
109
60
|
|
@@ -117,13 +68,14 @@ class Redis
|
|
117
68
|
# * brpoplpush
|
118
69
|
# * discard
|
119
70
|
# * move
|
71
|
+
# * sort
|
120
72
|
# * subscribe
|
121
73
|
# * psubscribe
|
122
74
|
# * publish
|
123
|
-
|
124
|
-
# * zunionstore
|
75
|
+
|
125
76
|
def flushdb
|
126
77
|
@data = ExpiringHash.new
|
78
|
+
"OK"
|
127
79
|
end
|
128
80
|
|
129
81
|
def flushall
|
@@ -160,6 +112,7 @@ class Redis
|
|
160
112
|
def bgreriteaof ; end
|
161
113
|
|
162
114
|
def get(key)
|
115
|
+
data_type_check(key, String)
|
163
116
|
@data[key]
|
164
117
|
end
|
165
118
|
|
@@ -175,13 +128,14 @@ class Redis
|
|
175
128
|
alias :substr :getrange
|
176
129
|
|
177
130
|
def getset(key, value)
|
178
|
-
|
179
|
-
@data[key]
|
180
|
-
|
131
|
+
data_type_check(key, String)
|
132
|
+
@data[key].tap do
|
133
|
+
set(key, value)
|
134
|
+
end
|
181
135
|
end
|
182
136
|
|
183
137
|
def mget(*keys)
|
184
|
-
raise
|
138
|
+
raise RuntimeError, "ERR wrong number of arguments for 'mget' command" if keys.empty?
|
185
139
|
@data.values_at(*keys)
|
186
140
|
end
|
187
141
|
|
@@ -254,7 +208,7 @@ class Redis
|
|
254
208
|
|
255
209
|
def lrange(key, startidx, endidx)
|
256
210
|
data_type_check(key, Array)
|
257
|
-
@data[key] && @data[key][startidx..endidx]
|
211
|
+
@data[key] && @data[key][startidx..endidx] || []
|
258
212
|
end
|
259
213
|
|
260
214
|
def ltrim(key, start, stop)
|
@@ -282,7 +236,7 @@ class Redis
|
|
282
236
|
def lset(key, index, value)
|
283
237
|
data_type_check(key, Array)
|
284
238
|
return unless @data[key]
|
285
|
-
raise RuntimeError if index >= @data[key].size
|
239
|
+
raise RuntimeError, "ERR index out of range" if index >= @data[key].size
|
286
240
|
@data[key][index] = value
|
287
241
|
end
|
288
242
|
|
@@ -307,7 +261,7 @@ class Redis
|
|
307
261
|
def rpush(key, value)
|
308
262
|
data_type_check(key, Array)
|
309
263
|
@data[key] ||= []
|
310
|
-
@data[key].push(value)
|
264
|
+
@data[key].push(value.to_s)
|
311
265
|
@data[key].size
|
312
266
|
end
|
313
267
|
|
@@ -320,7 +274,7 @@ class Redis
|
|
320
274
|
def lpush(key, value)
|
321
275
|
data_type_check(key, Array)
|
322
276
|
@data[key] ||= []
|
323
|
-
@data[key].unshift(value)
|
277
|
+
@data[key].unshift(value.to_s)
|
324
278
|
@data[key].size
|
325
279
|
end
|
326
280
|
|
@@ -338,8 +292,9 @@ class Redis
|
|
338
292
|
|
339
293
|
def rpoplpush(key1, key2)
|
340
294
|
data_type_check(key1, Array)
|
341
|
-
|
342
|
-
|
295
|
+
rpop(key1).tap do |elem|
|
296
|
+
lpush(key2, elem)
|
297
|
+
end
|
343
298
|
end
|
344
299
|
|
345
300
|
def lpop(key)
|
@@ -451,7 +406,7 @@ class Redis
|
|
451
406
|
keys.flatten.each do |key|
|
452
407
|
@data.delete(key)
|
453
408
|
end
|
454
|
-
|
409
|
+
old_count - @data.keys.size
|
455
410
|
end
|
456
411
|
|
457
412
|
def setnx(key, value)
|
@@ -523,7 +478,11 @@ class Redis
|
|
523
478
|
end
|
524
479
|
|
525
480
|
def hmset(key, *fields)
|
526
|
-
|
481
|
+
# mapped_hmset gives us [[:k1, "v1", :k2, "v2"]] for `fields`. Fix that.
|
482
|
+
fields = fields[0] if fields.size == 1 && fields[0].is_a?(Array)
|
483
|
+
fields = fields[0] if mapped_param?(fields)
|
484
|
+
raise RuntimeError, "ERR wrong number of arguments for HMSET" if fields.size > 2 && fields.size.odd?
|
485
|
+
raise RuntimeError, "ERR wrong number of arguments for 'hmset' command" if fields.empty? || fields.size.odd?
|
527
486
|
data_type_check(key, Hash)
|
528
487
|
@data[key] ||= {}
|
529
488
|
fields.each_slice(2) do |field|
|
@@ -532,9 +491,8 @@ class Redis
|
|
532
491
|
end
|
533
492
|
|
534
493
|
def hmget(key, *fields)
|
535
|
-
raise
|
494
|
+
raise RuntimeError, "ERR wrong number of arguments for 'hmget' command" if fields.empty?
|
536
495
|
data_type_check(key, Hash)
|
537
|
-
values = []
|
538
496
|
fields.map do |field|
|
539
497
|
field = field.to_s
|
540
498
|
if @data[key]
|
@@ -612,6 +570,8 @@ class Redis
|
|
612
570
|
end
|
613
571
|
|
614
572
|
def mset(*pairs)
|
573
|
+
# Handle pairs for mapped_mset command
|
574
|
+
pairs = pairs[0] if mapped_param?(pairs)
|
615
575
|
pairs.each_slice(2) do |pair|
|
616
576
|
@data[pair[0].to_s] = pair[1].to_s
|
617
577
|
end
|
@@ -619,9 +579,11 @@ class Redis
|
|
619
579
|
end
|
620
580
|
|
621
581
|
def msetnx(*pairs)
|
582
|
+
# Handle pairs for mapped_mset command
|
583
|
+
pairs = pairs[0] if mapped_param?(pairs)
|
622
584
|
keys = []
|
623
585
|
pairs.each_with_index{|item, index| keys << item.to_s if index % 2 == 0}
|
624
|
-
return if keys.any?{|key| @data.key?(key) }
|
586
|
+
return false if keys.any? {|key| @data.key?(key) }
|
625
587
|
mset(*pairs)
|
626
588
|
true
|
627
589
|
end
|
@@ -631,31 +593,27 @@ class Redis
|
|
631
593
|
end
|
632
594
|
|
633
595
|
def incr(key)
|
634
|
-
@data
|
635
|
-
@data[key] = (@data[key].to_i + 1).to_s
|
596
|
+
@data.merge!({ key => (@data[key].to_i + 1).to_s || "1"})
|
636
597
|
@data[key].to_i
|
637
598
|
end
|
638
599
|
|
639
600
|
def incrby(key, by)
|
640
|
-
@data
|
641
|
-
@data[key] = (@data[key].to_i + by.to_i).to_s
|
601
|
+
@data.merge!({ key => (@data[key].to_i + by.to_i).to_s || by })
|
642
602
|
@data[key].to_i
|
643
603
|
end
|
644
604
|
|
645
605
|
def decr(key)
|
646
|
-
@data
|
647
|
-
@data[key] = (@data[key].to_i - 1).to_s
|
606
|
+
@data.merge!({ key => (@data[key].to_i - 1).to_s || "-1"})
|
648
607
|
@data[key].to_i
|
649
608
|
end
|
650
609
|
|
651
610
|
def decrby(key, by)
|
652
|
-
@data
|
653
|
-
@data[key] = (@data[key].to_i - by.to_i).to_s
|
611
|
+
@data.merge!({ key => ((@data[key].to_i - by.to_i) || (by.to_i * -1)).to_s })
|
654
612
|
@data[key].to_i
|
655
613
|
end
|
656
614
|
|
657
615
|
def type(key)
|
658
|
-
case
|
616
|
+
case @data[key]
|
659
617
|
when nil then "none"
|
660
618
|
when String then "string"
|
661
619
|
when Hash then "hash"
|
@@ -694,6 +652,7 @@ class Redis
|
|
694
652
|
data_type_check(key, ZSet)
|
695
653
|
@data[key] ||= ZSet.new
|
696
654
|
exists = @data[key].key?(value.to_s)
|
655
|
+
score = "inf" if score == "+inf"
|
697
656
|
@data[key][value.to_s] = score
|
698
657
|
!exists
|
699
658
|
end
|
@@ -713,7 +672,8 @@ class Redis
|
|
713
672
|
|
714
673
|
def zscore(key, value)
|
715
674
|
data_type_check(key, ZSet)
|
716
|
-
@data[key] && @data[key][value.to_s]
|
675
|
+
result = @data[key] && @data[key][value.to_s]
|
676
|
+
result.to_s if result
|
717
677
|
end
|
718
678
|
|
719
679
|
def zcount(key, min, max)
|
@@ -726,7 +686,12 @@ class Redis
|
|
726
686
|
data_type_check(key, ZSet)
|
727
687
|
@data[key] ||= ZSet.new
|
728
688
|
@data[key][value.to_s] ||= 0
|
729
|
-
|
689
|
+
if %w(+inf -inf).include?(num)
|
690
|
+
num = "inf" if num == "+inf"
|
691
|
+
@data[key][value.to_s] = num
|
692
|
+
elsif ! %w(+inf -inf).include?(@data[key][value.to_s])
|
693
|
+
@data[key][value.to_s] += num
|
694
|
+
end
|
730
695
|
@data[key][value.to_s].to_s
|
731
696
|
end
|
732
697
|
|
@@ -744,11 +709,17 @@ class Redis
|
|
744
709
|
data_type_check(key, ZSet)
|
745
710
|
return [] unless @data[key]
|
746
711
|
|
747
|
-
if
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
712
|
+
# Sort by score, or if scores are equal, key alphanum
|
713
|
+
results = @data[key].sort do |(k1, v1), (k2, v2)|
|
714
|
+
if v1 == v2
|
715
|
+
k1 <=> k2
|
716
|
+
else
|
717
|
+
v1 <=> v2
|
718
|
+
end
|
719
|
+
end
|
720
|
+
# Select just the keys unless we want scores
|
721
|
+
results = results.map(&:first) unless with_scores
|
722
|
+
results[start..stop].flatten.map(&:to_s)
|
752
723
|
end
|
753
724
|
|
754
725
|
def zrevrange(key, start, stop, with_scores = nil)
|
@@ -805,31 +776,23 @@ class Redis
|
|
805
776
|
range.size
|
806
777
|
end
|
807
778
|
|
808
|
-
def zinterstore(out,
|
779
|
+
def zinterstore(out, *args)
|
809
780
|
data_type_check(out, ZSet)
|
781
|
+
args_handler = SortedSetArgumentHandler.new(args)
|
782
|
+
@data[out] = SortedSetIntersectStore.new(args_handler, @data).call
|
783
|
+
@data[out].size
|
784
|
+
end
|
810
785
|
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
when Hash
|
816
|
-
@data[src]
|
817
|
-
else
|
818
|
-
{}
|
819
|
-
end
|
820
|
-
end
|
821
|
-
|
822
|
-
@data[out] = ZSet.new
|
823
|
-
values = hashes.inject([]) {|r, h| r.empty? ? h.keys : r & h.keys }
|
824
|
-
values.each do |value|
|
825
|
-
@data[out][value] = hashes.inject(0) {|n, h| n + h[value].to_i }
|
826
|
-
end
|
827
|
-
|
786
|
+
def zunionstore(out, *args)
|
787
|
+
data_type_check(out, ZSet)
|
788
|
+
args_handler = SortedSetArgumentHandler.new(args)
|
789
|
+
@data[out] = SortedSetUnionStore.new(args_handler, @data).call
|
828
790
|
@data[out].size
|
829
791
|
end
|
830
792
|
|
831
793
|
def zremrangebyrank(key, start, stop)
|
832
|
-
sorted_elements = @data[key].sort { |(
|
794
|
+
sorted_elements = @data[key].sort { |(_, r_a), (_, r_b)| r_a <=> r_b }
|
795
|
+
start = sorted_elements.length if start > sorted_elements.length
|
833
796
|
elements_to_delete = sorted_elements[start..stop]
|
834
797
|
elements_to_delete.each { |elem, rank| @data[key].delete(elem) }
|
835
798
|
elements_to_delete.size
|
@@ -847,7 +810,8 @@ class Redis
|
|
847
810
|
|
848
811
|
def data_type_check(key, klass)
|
849
812
|
if @data[key] && !@data[key].is_a?(klass)
|
850
|
-
|
813
|
+
warn "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}."
|
814
|
+
raise RuntimeError.new("ERR Operation against a key holding the wrong kind of value")
|
851
815
|
end
|
852
816
|
end
|
853
817
|
|
@@ -863,6 +827,10 @@ class Redis
|
|
863
827
|
[offset, count]
|
864
828
|
end
|
865
829
|
end
|
830
|
+
|
831
|
+
def mapped_param? param
|
832
|
+
param.size == 1 && param[0].is_a?(Array)
|
833
|
+
end
|
866
834
|
end
|
867
835
|
end
|
868
836
|
end
|