mock_redis 0.5.4 → 0.31.0
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.
- 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
|