mock_redis 0.5.4 → 0.31.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/lint.yml +31 -0
- data/.github/workflows/tests.yml +63 -0
- data/.gitignore +1 -1
- data/.overcommit.yml +21 -0
- data/.rspec +1 -1
- data/.rubocop.yml +148 -0
- data/.rubocop_todo.yml +35 -0
- data/.simplecov +4 -0
- data/CHANGELOG.md +278 -0
- data/Gemfile +9 -5
- data/LICENSE.md +21 -0
- data/README.md +52 -16
- data/Rakefile +0 -8
- data/lib/mock_redis/assertions.rb +0 -1
- data/lib/mock_redis/connection_method.rb +13 -0
- data/lib/mock_redis/database.rb +193 -257
- data/lib/mock_redis/expire_wrapper.rb +2 -2
- data/lib/mock_redis/future.rb +23 -0
- data/lib/mock_redis/geospatial_methods.rb +240 -0
- data/lib/mock_redis/hash_methods.rb +83 -24
- data/lib/mock_redis/indifferent_hash.rb +11 -0
- data/lib/mock_redis/info_method.rb +160 -0
- data/lib/mock_redis/list_methods.rb +34 -19
- data/lib/mock_redis/multi_db_wrapper.rb +8 -7
- data/lib/mock_redis/pipelined_wrapper.rb +42 -16
- data/lib/mock_redis/set_methods.rb +62 -19
- data/lib/mock_redis/sort_method.rb +81 -0
- data/lib/mock_redis/stream/id.rb +58 -0
- data/lib/mock_redis/stream.rb +88 -0
- data/lib/mock_redis/stream_methods.rb +102 -0
- data/lib/mock_redis/string_methods.rb +235 -42
- data/lib/mock_redis/transaction_wrapper.rb +62 -28
- data/lib/mock_redis/utility_methods.rb +62 -11
- data/lib/mock_redis/version.rb +4 -1
- data/lib/mock_redis/zset.rb +24 -29
- data/lib/mock_redis/zset_methods.rb +187 -59
- data/lib/mock_redis.rb +77 -27
- data/mock_redis.gemspec +23 -15
- data/spec/client_spec.rb +29 -0
- data/spec/cloning_spec.rb +17 -18
- data/spec/commands/append_spec.rb +4 -4
- data/spec/commands/auth_spec.rb +1 -1
- data/spec/commands/bgrewriteaof_spec.rb +2 -2
- data/spec/commands/bgsave_spec.rb +2 -2
- data/spec/commands/bitcount_spec.rb +25 -0
- data/spec/commands/bitfield_spec.rb +169 -0
- data/spec/commands/blpop_spec.rb +19 -21
- data/spec/commands/brpop_spec.rb +25 -20
- data/spec/commands/brpoplpush_spec.rb +16 -17
- data/spec/commands/connected_spec.rb +7 -0
- data/spec/commands/connection_spec.rb +15 -0
- data/spec/commands/dbsize_spec.rb +3 -3
- data/spec/commands/decr_spec.rb +8 -8
- data/spec/commands/decrby_spec.rb +8 -8
- data/spec/commands/del_spec.rb +35 -3
- data/spec/commands/disconnect_spec.rb +7 -0
- data/spec/commands/dump_spec.rb +19 -0
- data/spec/commands/echo_spec.rb +4 -4
- data/spec/commands/eval_spec.rb +7 -0
- data/spec/commands/evalsha_spec.rb +10 -0
- data/spec/commands/exists_spec.rb +36 -7
- data/spec/commands/expire_spec.rb +48 -20
- data/spec/commands/expireat_spec.rb +12 -13
- data/spec/commands/flushall_spec.rb +5 -5
- data/spec/commands/flushdb_spec.rb +5 -5
- data/spec/commands/future_spec.rb +30 -0
- data/spec/commands/geoadd_spec.rb +58 -0
- data/spec/commands/geodist_spec.rb +118 -0
- data/spec/commands/geohash_spec.rb +52 -0
- data/spec/commands/geopos_spec.rb +55 -0
- data/spec/commands/get_spec.rb +14 -6
- data/spec/commands/getbit_spec.rb +7 -7
- data/spec/commands/getrange_spec.rb +9 -9
- data/spec/commands/getset_spec.rb +7 -7
- data/spec/commands/hdel_spec.rb +41 -11
- data/spec/commands/hexists_spec.rb +11 -11
- data/spec/commands/hget_spec.rb +7 -7
- data/spec/commands/hgetall_spec.rb +15 -5
- data/spec/commands/hincrby_spec.rb +16 -16
- data/spec/commands/hincrbyfloat_spec.rb +58 -0
- data/spec/commands/hkeys_spec.rb +5 -5
- data/spec/commands/hlen_spec.rb +5 -5
- data/spec/commands/hmget_spec.rb +19 -9
- data/spec/commands/hmset_spec.rb +38 -12
- data/spec/commands/hscan_each_spec.rb +48 -0
- data/spec/commands/hscan_spec.rb +27 -0
- data/spec/commands/hset_spec.rb +26 -12
- data/spec/commands/hsetnx_spec.rb +16 -16
- data/spec/commands/hvals_spec.rb +5 -5
- data/spec/commands/incr_spec.rb +8 -8
- data/spec/commands/incrby_spec.rb +13 -13
- data/spec/commands/incrbyfloat_spec.rb +13 -13
- data/spec/commands/info_spec.rb +54 -5
- data/spec/commands/keys_spec.rb +83 -31
- data/spec/commands/lastsave_spec.rb +2 -2
- data/spec/commands/lindex_spec.rb +20 -10
- data/spec/commands/linsert_spec.rb +14 -14
- data/spec/commands/llen_spec.rb +4 -4
- data/spec/commands/lpop_spec.rb +6 -6
- data/spec/commands/lpush_spec.rb +21 -15
- data/spec/commands/lpushx_spec.rb +24 -11
- data/spec/commands/lrange_spec.rb +24 -8
- data/spec/commands/lrem_spec.rb +16 -16
- data/spec/commands/lset_spec.rb +17 -12
- data/spec/commands/ltrim_spec.rb +17 -7
- data/spec/commands/mapped_hmget_spec.rb +13 -9
- data/spec/commands/mapped_hmset_spec.rb +12 -12
- data/spec/commands/mapped_mget_spec.rb +22 -0
- data/spec/commands/mapped_mset_spec.rb +19 -0
- data/spec/commands/mapped_msetnx_spec.rb +26 -0
- data/spec/commands/mget_spec.rb +48 -17
- data/spec/commands/move_spec.rb +37 -37
- data/spec/commands/mset_spec.rb +20 -6
- data/spec/commands/msetnx_spec.rb +14 -14
- data/spec/commands/persist_spec.rb +15 -16
- data/spec/commands/pexpire_spec.rb +86 -0
- data/spec/commands/pexpireat_spec.rb +48 -0
- data/spec/commands/ping_spec.rb +6 -2
- data/spec/commands/pipelined_spec.rb +98 -7
- data/spec/commands/pttl_spec.rb +41 -0
- data/spec/commands/randomkey_spec.rb +3 -3
- data/spec/commands/rename_spec.rb +16 -12
- data/spec/commands/renamenx_spec.rb +13 -15
- data/spec/commands/restore_spec.rb +47 -0
- data/spec/commands/rpop_spec.rb +6 -6
- data/spec/commands/rpoplpush_spec.rb +13 -8
- data/spec/commands/rpush_spec.rb +21 -15
- data/spec/commands/rpushx_spec.rb +24 -11
- data/spec/commands/sadd_spec.rb +14 -10
- data/spec/commands/scan_each_spec.rb +39 -0
- data/spec/commands/scan_spec.rb +64 -0
- data/spec/commands/scard_spec.rb +3 -3
- data/spec/commands/script_spec.rb +9 -0
- data/spec/commands/sdiff_spec.rb +13 -13
- data/spec/commands/sdiffstore_spec.rb +13 -13
- data/spec/commands/select_spec.rb +13 -5
- data/spec/commands/set_spec.rb +112 -0
- data/spec/commands/setbit_spec.rb +25 -16
- data/spec/commands/setex_spec.rb +20 -4
- data/spec/commands/setnx_spec.rb +6 -6
- data/spec/commands/setrange_spec.rb +12 -12
- data/spec/commands/sinter_spec.rb +11 -13
- data/spec/commands/sinterstore_spec.rb +12 -12
- data/spec/commands/sismember_spec.rb +10 -10
- data/spec/commands/smembers_spec.rb +15 -5
- data/spec/commands/smove_spec.rb +13 -13
- data/spec/commands/sort_list_spec.rb +21 -0
- data/spec/commands/sort_set_spec.rb +21 -0
- data/spec/commands/sort_zset_spec.rb +21 -0
- data/spec/commands/spop_spec.rb +19 -4
- data/spec/commands/srandmember_spec.rb +28 -4
- data/spec/commands/srem_spec.rb +17 -12
- data/spec/commands/sscan_each_spec.rb +48 -0
- data/spec/commands/sscan_spec.rb +39 -0
- data/spec/commands/strlen_spec.rb +4 -5
- data/spec/commands/sunion_spec.rb +13 -11
- data/spec/commands/sunionstore_spec.rb +12 -12
- data/spec/commands/ttl_spec.rb +11 -6
- data/spec/commands/type_spec.rb +1 -1
- data/spec/commands/watch_spec.rb +9 -4
- data/spec/commands/xadd_spec.rb +122 -0
- data/spec/commands/xlen_spec.rb +22 -0
- data/spec/commands/xrange_spec.rb +164 -0
- data/spec/commands/xread_spec.rb +66 -0
- data/spec/commands/xrevrange_spec.rb +130 -0
- data/spec/commands/xtrim_spec.rb +36 -0
- data/spec/commands/zadd_spec.rb +100 -11
- data/spec/commands/zcard_spec.rb +4 -4
- data/spec/commands/zcount_spec.rb +18 -10
- data/spec/commands/zincrby_spec.rb +6 -6
- data/spec/commands/zinterstore_spec.rb +54 -20
- data/spec/commands/zpopmax_spec.rb +60 -0
- data/spec/commands/zpopmin_spec.rb +60 -0
- data/spec/commands/zrange_spec.rb +54 -13
- data/spec/commands/zrangebyscore_spec.rb +42 -27
- data/spec/commands/zrank_spec.rb +4 -4
- data/spec/commands/zrem_spec.rb +18 -12
- data/spec/commands/zremrangebyrank_spec.rb +5 -5
- data/spec/commands/zremrangebyscore_spec.rb +12 -5
- data/spec/commands/zrevrange_spec.rb +35 -10
- data/spec/commands/zrevrangebyscore_spec.rb +26 -15
- data/spec/commands/zrevrank_spec.rb +4 -4
- data/spec/commands/zscan_each_spec.rb +48 -0
- data/spec/commands/zscan_spec.rb +26 -0
- data/spec/commands/zscore_spec.rb +7 -7
- data/spec/commands/zunionstore_spec.rb +54 -21
- data/spec/mock_redis_spec.rb +61 -0
- data/spec/spec_helper.rb +35 -8
- data/spec/support/redis_multiplexer.rb +62 -37
- data/spec/support/shared_examples/does_not_cleanup_empty_strings.rb +14 -0
- data/spec/support/shared_examples/only_operates_on_hashes.rb +5 -3
- data/spec/support/shared_examples/only_operates_on_lists.rb +5 -3
- data/spec/support/shared_examples/only_operates_on_sets.rb +5 -3
- data/spec/support/shared_examples/only_operates_on_strings.rb +4 -4
- data/spec/support/shared_examples/only_operates_on_zsets.rb +18 -16
- data/spec/support/shared_examples/sorts_enumerables.rb +56 -0
- data/spec/transactions_spec.rb +79 -29
- metadata +162 -42
- data/LICENSE +0 -19
- data/spec/commands/hash_operator_spec.rb +0 -21
@@ -3,23 +3,74 @@ class MockRedis
|
|
3
3
|
private
|
4
4
|
|
5
5
|
def with_thing_at(key, assertion, empty_thing_generator)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
send(assertion, key)
|
7
|
+
data[key] ||= empty_thing_generator.call
|
8
|
+
data_key_ref = data[key]
|
9
|
+
ret = yield data[key]
|
10
|
+
data[key] = data_key_ref if data[key].nil?
|
11
|
+
primitive?(ret) ? ret.dup : ret
|
12
|
+
ensure
|
13
|
+
clean_up_empties_at(key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def primitive?(value)
|
17
|
+
value.is_a?(::Array) || value.is_a?(::Hash) || value.is_a?(::String)
|
16
18
|
end
|
17
19
|
|
18
20
|
def clean_up_empties_at(key)
|
19
|
-
if data[key] && data[key].
|
21
|
+
if data[key]&.empty? && data[key] != '' && !data[key].is_a?(Stream)
|
20
22
|
del(key)
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
26
|
+
def common_scan(values, cursor, opts = {})
|
27
|
+
count = (opts[:count] || 10).to_i
|
28
|
+
cursor = cursor.to_i
|
29
|
+
match = opts[:match] || '*'
|
30
|
+
key = opts[:key] || lambda { |x| x }
|
31
|
+
filtered_values = []
|
32
|
+
|
33
|
+
limit = cursor + count
|
34
|
+
next_cursor = limit >= values.length ? '0' : limit.to_s
|
35
|
+
|
36
|
+
unless values[cursor...limit].nil?
|
37
|
+
filtered_values = values[cursor...limit].select do |val|
|
38
|
+
redis_pattern_to_ruby_regex(match).match(key.call(val))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
[next_cursor, filtered_values]
|
43
|
+
end
|
44
|
+
|
45
|
+
def twos_complement_encode(n, size)
|
46
|
+
if n < 0
|
47
|
+
str = (n + 1).abs.to_s(2)
|
48
|
+
|
49
|
+
binary = left_pad(str, size - 1).chars.map { |c| c == '0' ? 1 : 0 }
|
50
|
+
binary.unshift(1)
|
51
|
+
else
|
52
|
+
binary = left_pad(n.abs.to_s(2), size - 1).chars.map(&:to_i)
|
53
|
+
binary.unshift(0)
|
54
|
+
end
|
55
|
+
|
56
|
+
binary
|
57
|
+
end
|
58
|
+
|
59
|
+
def twos_complement_decode(array)
|
60
|
+
total = 0
|
61
|
+
|
62
|
+
array.each.with_index do |bit, index|
|
63
|
+
total += 2**(array.length - index - 1) if bit == 1
|
64
|
+
total = -total if index == 0
|
65
|
+
end
|
66
|
+
|
67
|
+
total
|
68
|
+
end
|
69
|
+
|
70
|
+
def left_pad(str, size)
|
71
|
+
str = '0' + str while str.length < size
|
72
|
+
|
73
|
+
str
|
74
|
+
end
|
24
75
|
end
|
25
76
|
end
|
data/lib/mock_redis/version.rb
CHANGED
data/lib/mock_redis/zset.rb
CHANGED
@@ -12,7 +12,7 @@ class MockRedis
|
|
12
12
|
|
13
13
|
def initialize
|
14
14
|
@members = Set.new
|
15
|
-
@scores =
|
15
|
+
@scores = {}
|
16
16
|
end
|
17
17
|
|
18
18
|
def initialize_copy(source)
|
@@ -23,49 +23,45 @@ class MockRedis
|
|
23
23
|
|
24
24
|
def add(score, member)
|
25
25
|
members.add(member)
|
26
|
-
|
27
|
-
scores[member] = score.to_f.to_i
|
28
|
-
else
|
29
|
-
scores[member] = score.to_f
|
30
|
-
end
|
26
|
+
scores[member] = score.to_f
|
31
27
|
self
|
32
28
|
end
|
33
29
|
|
34
30
|
def delete?(member)
|
35
31
|
scores.delete(member)
|
36
|
-
members.delete?(member)
|
32
|
+
members.delete?(member) && self
|
37
33
|
end
|
38
34
|
|
39
35
|
def each
|
40
|
-
members.each {|m| yield score(m), m}
|
36
|
+
members.each { |m| yield score(m), m }
|
41
37
|
end
|
42
38
|
|
43
39
|
def in_range(min, max)
|
44
40
|
in_from_the_left = case min
|
45
|
-
when
|
46
|
-
|
47
|
-
when
|
48
|
-
|
41
|
+
when '-inf'
|
42
|
+
lambda { |_| true }
|
43
|
+
when '+inf'
|
44
|
+
lambda { |_| false }
|
49
45
|
when /\((.*)$/
|
50
|
-
|
51
|
-
lambda {|x| x.to_f >
|
46
|
+
left_val = $1.to_f
|
47
|
+
lambda { |x| x.to_f > left_val }
|
52
48
|
else
|
53
|
-
lambda {|x| x.to_f >= min.to_f }
|
49
|
+
lambda { |x| x.to_f >= min.to_f }
|
54
50
|
end
|
55
51
|
|
56
52
|
in_from_the_right = case max
|
57
|
-
when
|
58
|
-
|
59
|
-
when
|
60
|
-
|
53
|
+
when '-inf'
|
54
|
+
lambda { |_| false }
|
55
|
+
when '+inf'
|
56
|
+
lambda { |_| true }
|
61
57
|
when /\((.*)$/
|
62
|
-
|
63
|
-
lambda {|x| x.to_f <
|
58
|
+
right_val = $1.to_f
|
59
|
+
lambda { |x| x.to_f < right_val }
|
64
60
|
else
|
65
|
-
lambda {|x| x.to_f <= max.to_f }
|
61
|
+
lambda { |x| x.to_f <= max.to_f }
|
66
62
|
end
|
67
63
|
|
68
|
-
sorted.find_all do |(score,
|
64
|
+
sorted.find_all do |(score, _member)|
|
69
65
|
in_from_the_left[score] && in_from_the_right[score]
|
70
66
|
end
|
71
67
|
end
|
@@ -74,8 +70,8 @@ class MockRedis
|
|
74
70
|
if !block_given?
|
75
71
|
intersection(other, &:+)
|
76
72
|
else
|
77
|
-
|
78
|
-
new_score = yield(
|
73
|
+
members.intersection(other.members).reduce(self.class.new) do |acc, m|
|
74
|
+
new_score = yield(score(m), other.score(m))
|
79
75
|
acc.add(new_score, m)
|
80
76
|
end
|
81
77
|
end
|
@@ -88,7 +84,7 @@ class MockRedis
|
|
88
84
|
def sorted
|
89
85
|
members.map do |m|
|
90
86
|
[score(m), m]
|
91
|
-
end.
|
87
|
+
end.sort
|
92
88
|
end
|
93
89
|
|
94
90
|
def sorted_members
|
@@ -99,12 +95,11 @@ class MockRedis
|
|
99
95
|
if !block_given?
|
100
96
|
union(other, &:+)
|
101
97
|
else
|
102
|
-
|
103
|
-
new_score = yield(
|
98
|
+
members.union(other.members).reduce(self.class.new) do |acc, m|
|
99
|
+
new_score = yield(score(m), other.score(m))
|
104
100
|
acc.add(new_score, m)
|
105
101
|
end
|
106
102
|
end
|
107
103
|
end
|
108
|
-
|
109
104
|
end
|
110
105
|
end
|
@@ -8,42 +8,90 @@ class MockRedis
|
|
8
8
|
include UtilityMethods
|
9
9
|
|
10
10
|
def zadd(key, *args)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
else
|
18
|
-
unless args.all? {|pair| pair.size == 2 }
|
19
|
-
raise(Redis::CommandError, "ERR syntax error")
|
20
|
-
end
|
11
|
+
zadd_options = {}
|
12
|
+
zadd_options = args.pop if args.last.is_a?(Hash)
|
13
|
+
|
14
|
+
if zadd_options&.include?(:nx) && zadd_options&.include?(:xx)
|
15
|
+
raise Redis::CommandError, 'ERR XX and NX options at the same time are not compatible'
|
21
16
|
end
|
22
17
|
|
23
|
-
if args.size ==
|
18
|
+
if args.size == 1 && args[0].is_a?(Array)
|
19
|
+
zadd_multiple_members(key, args.first, zadd_options)
|
20
|
+
elsif args.size == 2
|
24
21
|
score, member = args
|
25
|
-
|
26
|
-
retval = !zscore(key, member)
|
27
|
-
with_zset_at(key) {|z| z.add(score, member.to_s)}
|
22
|
+
zadd_one_member(key, score, member, zadd_options)
|
28
23
|
else
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
24
|
+
raise Redis::CommandError, 'ERR wrong number of arguments'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def zadd_one_member(key, score, member, zadd_options = {})
|
29
|
+
assert_scorey(score) unless score.to_s =~ /(\+|\-)inf/
|
30
|
+
|
31
|
+
with_zset_at(key) do |zset|
|
32
|
+
if zadd_options[:incr]
|
33
|
+
if zadd_options[:xx]
|
34
|
+
member_present = zset.include?(member)
|
35
|
+
return member_present ? zincrby(key, score, member) : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
if zadd_options[:nx]
|
39
|
+
member_present = zset.include?(member)
|
40
|
+
return member_present ? nil : zincrby(key, score, member)
|
41
|
+
end
|
42
|
+
|
43
|
+
zincrby(key, score, member)
|
44
|
+
elsif zadd_options[:xx]
|
45
|
+
zset.add(score, member.to_s) if zset.include?(member)
|
46
|
+
false
|
47
|
+
elsif zadd_options[:nx]
|
48
|
+
!zset.include?(member) && !!zset.add(score, member.to_s)
|
49
|
+
else
|
50
|
+
retval = !zscore(key, member)
|
51
|
+
zset.add(score, member.to_s)
|
52
|
+
retval
|
34
53
|
end
|
35
54
|
end
|
55
|
+
end
|
36
56
|
|
37
|
-
|
57
|
+
private :zadd_one_member
|
58
|
+
|
59
|
+
def zadd_multiple_members(key, args, zadd_options = {})
|
60
|
+
assert_has_args(args, 'zadd')
|
61
|
+
|
62
|
+
args = args.each_slice(2).to_a unless args.first.is_a?(Array)
|
63
|
+
with_zset_at(key) do |zset|
|
64
|
+
if zadd_options[:incr]
|
65
|
+
raise Redis::CommandError, 'ERR INCR option supports a single increment-element pair'
|
66
|
+
elsif zadd_options[:xx]
|
67
|
+
args.each { |score, member| zset.include?(member) && zset.add(score, member.to_s) }
|
68
|
+
0
|
69
|
+
elsif zadd_options[:nx]
|
70
|
+
args.reduce(0) do |retval, (score, member)|
|
71
|
+
unless zset.include?(member)
|
72
|
+
zset.add(score, member.to_s)
|
73
|
+
retval += 1
|
74
|
+
end
|
75
|
+
retval
|
76
|
+
end
|
77
|
+
else
|
78
|
+
args.reduce(0) do |retval, (score, member)|
|
79
|
+
retval += 1 unless zset.include?(member)
|
80
|
+
zset.add(score, member.to_s)
|
81
|
+
retval
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
38
85
|
end
|
39
86
|
|
87
|
+
private :zadd_multiple_members
|
88
|
+
|
40
89
|
def zcard(key)
|
41
90
|
with_zset_at(key, &:size)
|
42
91
|
end
|
43
92
|
|
44
93
|
def zcount(key, min, max)
|
45
|
-
|
46
|
-
assert_scorey(max, 'ERR min or max is not a float') unless max == '+inf'
|
94
|
+
assert_range_args(min, max)
|
47
95
|
|
48
96
|
with_zset_at(key) do |zset|
|
49
97
|
zset.in_range(min, max).size
|
@@ -61,20 +109,24 @@ class MockRedis
|
|
61
109
|
end
|
62
110
|
end
|
63
111
|
|
64
|
-
def zinterstore(destination, keys, options={})
|
112
|
+
def zinterstore(destination, keys, options = {})
|
65
113
|
assert_has_args(keys, 'zinterstore')
|
66
114
|
|
67
115
|
data[destination] = combine_weighted_zsets(keys, options, :intersection)
|
68
116
|
zcard(destination)
|
69
117
|
end
|
70
118
|
|
71
|
-
def zrange(key, start, stop, options={})
|
119
|
+
def zrange(key, start, stop, options = {})
|
72
120
|
with_zset_at(key) do |z|
|
73
|
-
|
121
|
+
start = [start.to_i, -z.sorted.size].max
|
122
|
+
stop = stop.to_i
|
123
|
+
to_response(z.sorted[start..stop] || [], options)
|
74
124
|
end
|
75
125
|
end
|
76
126
|
|
77
|
-
def zrangebyscore(key, min, max, options={})
|
127
|
+
def zrangebyscore(key, min, max, options = {})
|
128
|
+
assert_range_args(min, max)
|
129
|
+
|
78
130
|
with_zset_at(key) do |zset|
|
79
131
|
all_results = zset.in_range(min, max)
|
80
132
|
to_response(apply_limit(all_results, options[:limit]), options)
|
@@ -82,63 +134,106 @@ class MockRedis
|
|
82
134
|
end
|
83
135
|
|
84
136
|
def zrank(key, member)
|
85
|
-
with_zset_at(key) {|z| z.sorted_members.index(member.to_s) }
|
137
|
+
with_zset_at(key) { |z| z.sorted_members.index(member.to_s) }
|
86
138
|
end
|
87
139
|
|
88
140
|
def zrem(key, *args)
|
89
141
|
if !args.first.is_a?(Array)
|
90
|
-
retval = with_zset_at(key) {|z| !!z.delete?(args.first.to_s)}
|
142
|
+
retval = with_zset_at(key) { |z| !!z.delete?(args.first.to_s) }
|
91
143
|
else
|
92
144
|
args = args.first
|
93
|
-
|
94
|
-
|
95
|
-
|
145
|
+
if args.empty?
|
146
|
+
raise Redis::CommandError, "ERR wrong number of arguments for 'zrem' command"
|
147
|
+
else
|
148
|
+
retval = args.map { |member| !!zscore(key, member.to_s) }.count(true)
|
149
|
+
with_zset_at(key) do |z|
|
150
|
+
args.each { |member| z.delete?(member) }
|
151
|
+
end
|
96
152
|
end
|
97
153
|
end
|
98
154
|
|
99
155
|
retval
|
100
156
|
end
|
101
157
|
|
102
|
-
def
|
158
|
+
def zpopmin(key, count = 1)
|
103
159
|
with_zset_at(key) do |z|
|
104
|
-
|
160
|
+
pairs = z.sorted.first(count)
|
161
|
+
pairs.each { |pair| z.delete?(pair.last) }
|
162
|
+
retval = to_response(pairs, with_scores: true)
|
163
|
+
count == 1 ? retval.first : retval
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def zpopmax(key, count = 1)
|
168
|
+
with_zset_at(key) do |z|
|
169
|
+
pairs = z.sorted.reverse.first(count)
|
170
|
+
pairs.each { |pair| z.delete?(pair.last) }
|
171
|
+
retval = to_response(pairs, with_scores: true)
|
172
|
+
count == 1 ? retval.first : retval
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def zrevrange(key, start, stop, options = {})
|
177
|
+
with_zset_at(key) do |z|
|
178
|
+
to_response(z.sorted.reverse[start..stop] || [], options)
|
105
179
|
end
|
106
180
|
end
|
107
181
|
|
108
182
|
def zremrangebyrank(key, start, stop)
|
109
183
|
zrange(key, start, stop).
|
110
|
-
each {|member| zrem(key, member)}.
|
184
|
+
each { |member| zrem(key, member) }.
|
111
185
|
size
|
112
186
|
end
|
113
187
|
|
114
188
|
def zremrangebyscore(key, min, max)
|
189
|
+
assert_range_args(min, max)
|
190
|
+
|
115
191
|
zrangebyscore(key, min, max).
|
116
|
-
each {|member| zrem(key, member)}.
|
192
|
+
each { |member| zrem(key, member) }.
|
117
193
|
size
|
118
194
|
end
|
119
195
|
|
120
|
-
def zrevrangebyscore(key, max, min, options={})
|
196
|
+
def zrevrangebyscore(key, max, min, options = {})
|
197
|
+
assert_range_args(min, max)
|
198
|
+
|
121
199
|
with_zset_at(key) do |zset|
|
122
200
|
to_response(
|
123
201
|
apply_limit(
|
124
202
|
zset.in_range(min, max).reverse,
|
125
|
-
options[:limit]
|
126
|
-
|
203
|
+
options[:limit]
|
204
|
+
),
|
205
|
+
options
|
206
|
+
)
|
127
207
|
end
|
128
208
|
end
|
129
209
|
|
130
210
|
def zrevrank(key, member)
|
131
|
-
with_zset_at(key) {|z| z.sorted_members.reverse.index(member.to_s) }
|
211
|
+
with_zset_at(key) { |z| z.sorted_members.reverse.index(member.to_s) }
|
212
|
+
end
|
213
|
+
|
214
|
+
def zscan(key, cursor, opts = {})
|
215
|
+
opts = opts.merge(key: lambda { |x| x[0] })
|
216
|
+
common_scan(zrange(key, 0, -1, withscores: true), cursor, opts)
|
217
|
+
end
|
218
|
+
|
219
|
+
def zscan_each(key, opts = {}, &block)
|
220
|
+
return to_enum(:zscan_each, key, opts) unless block_given?
|
221
|
+
cursor = 0
|
222
|
+
loop do
|
223
|
+
cursor, values = zscan(key, cursor, opts)
|
224
|
+
values.each(&block)
|
225
|
+
break if cursor == '0'
|
226
|
+
end
|
132
227
|
end
|
133
228
|
|
134
229
|
def zscore(key, member)
|
135
230
|
with_zset_at(key) do |z|
|
136
231
|
score = z.score(member.to_s)
|
137
|
-
score
|
232
|
+
score&.to_f
|
138
233
|
end
|
139
234
|
end
|
140
235
|
|
141
|
-
def zunionstore(destination, keys, options={})
|
236
|
+
def zunionstore(destination, keys, options = {})
|
142
237
|
assert_has_args(keys, 'zunionstore')
|
143
238
|
|
144
239
|
data[destination] = combine_weighted_zsets(keys, options, :union)
|
@@ -146,13 +241,14 @@ class MockRedis
|
|
146
241
|
end
|
147
242
|
|
148
243
|
private
|
244
|
+
|
149
245
|
def apply_limit(collection, limit)
|
150
246
|
if limit
|
151
247
|
if limit.is_a?(Array) && limit.length == 2
|
152
248
|
offset, count = limit
|
153
249
|
collection.drop(offset).take(count)
|
154
250
|
else
|
155
|
-
raise Redis::CommandError,
|
251
|
+
raise Redis::CommandError, 'ERR syntax error'
|
156
252
|
end
|
157
253
|
else
|
158
254
|
collection
|
@@ -160,7 +256,7 @@ class MockRedis
|
|
160
256
|
end
|
161
257
|
|
162
258
|
def to_response(score_member_pairs, options)
|
163
|
-
score_member_pairs.map do |(score,member)|
|
259
|
+
score_member_pairs.map do |(score, member)|
|
164
260
|
if options[:with_scores] || options[:withscores]
|
165
261
|
[member, score.to_f]
|
166
262
|
else
|
@@ -172,21 +268,21 @@ class MockRedis
|
|
172
268
|
def combine_weighted_zsets(keys, options, how)
|
173
269
|
weights = options.fetch(:weights, keys.map { 1 })
|
174
270
|
if weights.length != keys.length
|
175
|
-
raise Redis::CommandError,
|
271
|
+
raise Redis::CommandError, 'ERR syntax error'
|
176
272
|
end
|
177
273
|
|
178
274
|
aggregator = case options.fetch(:aggregate, :sum).to_s.downcase.to_sym
|
179
275
|
when :sum
|
180
|
-
proc {|a,b| [a,b].compact.reduce(&:+)}
|
276
|
+
proc { |a, b| [a, b].compact.reduce(&:+) }
|
181
277
|
when :min
|
182
|
-
proc {|a,b| [a,b].compact.min}
|
278
|
+
proc { |a, b| [a, b].compact.min }
|
183
279
|
when :max
|
184
|
-
proc {|a,b| [a,b].compact.max}
|
280
|
+
proc { |a, b| [a, b].compact.max }
|
185
281
|
else
|
186
|
-
raise Redis::CommandError,
|
282
|
+
raise Redis::CommandError, 'ERR syntax error'
|
187
283
|
end
|
188
284
|
|
189
|
-
with_zsets_at(*keys) do |*zsets|
|
285
|
+
with_zsets_at(*keys, coercible: true) do |*zsets|
|
190
286
|
zsets.zip(weights).map do |(zset, weight)|
|
191
287
|
zset.reduce(Zset.new) do |acc, (score, member)|
|
192
288
|
acc.add(score * weight, member)
|
@@ -195,33 +291,57 @@ class MockRedis
|
|
195
291
|
za.send(how, zb, &aggregator)
|
196
292
|
end
|
197
293
|
end
|
294
|
+
end
|
198
295
|
|
296
|
+
def coerce_to_zset(set)
|
297
|
+
zset = Zset.new
|
298
|
+
set.each do |member|
|
299
|
+
zset.add(1.0, member)
|
300
|
+
end
|
301
|
+
zset
|
199
302
|
end
|
200
303
|
|
201
|
-
def with_zset_at(key, &blk)
|
202
|
-
|
304
|
+
def with_zset_at(key, coercible: false, &blk)
|
305
|
+
if coercible
|
306
|
+
with_thing_at(key, :assert_coercible_zsety, proc { Zset.new }) do |value|
|
307
|
+
blk.call value.is_a?(Set) ? coerce_to_zset(value) : value
|
308
|
+
end
|
309
|
+
else
|
310
|
+
with_thing_at(key, :assert_zsety, proc { Zset.new }, &blk)
|
311
|
+
end
|
203
312
|
end
|
204
313
|
|
205
|
-
def with_zsets_at(*keys, &blk)
|
314
|
+
def with_zsets_at(*keys, coercible: false, &blk)
|
206
315
|
if keys.length == 1
|
207
|
-
with_zset_at(keys.first, &blk)
|
316
|
+
with_zset_at(keys.first, coercible: coercible, &blk)
|
208
317
|
else
|
209
|
-
with_zset_at(keys.first) do |set|
|
210
|
-
with_zsets_at(*(keys[1..-1])) do |*sets|
|
211
|
-
|
318
|
+
with_zset_at(keys.first, coercible: coercible) do |set|
|
319
|
+
with_zsets_at(*(keys[1..-1]), coercible: coercible) do |*sets|
|
320
|
+
yield(*([set] + sets))
|
212
321
|
end
|
213
322
|
end
|
214
323
|
end
|
215
324
|
end
|
216
325
|
|
217
326
|
def zsety?(key)
|
218
|
-
data[key].nil? || data[key].
|
327
|
+
data[key].nil? || data[key].is_a?(Zset)
|
328
|
+
end
|
329
|
+
|
330
|
+
def coercible_zsety?(key)
|
331
|
+
zsety?(key) || data[key].is_a?(Set)
|
219
332
|
end
|
220
333
|
|
221
334
|
def assert_zsety(key)
|
222
335
|
unless zsety?(key)
|
223
336
|
raise Redis::CommandError,
|
224
|
-
|
337
|
+
'WRONGTYPE Operation against a key holding the wrong kind of value'
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def assert_coercible_zsety(key)
|
342
|
+
unless coercible_zsety?(key)
|
343
|
+
raise Redis::CommandError,
|
344
|
+
'WRONGTYPE Operation against a key holding the wrong kind of value'
|
225
345
|
end
|
226
346
|
end
|
227
347
|
|
@@ -230,11 +350,19 @@ class MockRedis
|
|
230
350
|
!!Float(x) rescue false
|
231
351
|
end
|
232
352
|
|
233
|
-
def assert_scorey(value, message =
|
353
|
+
def assert_scorey(value, message = 'ERR value is not a valid float')
|
354
|
+
return if value.to_s =~ /\(?(\-|\+)inf/
|
355
|
+
|
356
|
+
value = $1 if value.to_s =~ /\((.*)/
|
234
357
|
unless looks_like_float?(value)
|
235
358
|
raise Redis::CommandError, message
|
236
359
|
end
|
237
360
|
end
|
238
361
|
|
362
|
+
def assert_range_args(min, max)
|
363
|
+
[min, max].each do |value|
|
364
|
+
assert_scorey(value, 'ERR min or max is not a float')
|
365
|
+
end
|
366
|
+
end
|
239
367
|
end
|
240
368
|
end
|