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
@@ -0,0 +1,240 @@
|
|
1
|
+
class MockRedis
|
2
|
+
module GeospatialMethods
|
3
|
+
LNG_RANGE = (-180..180).freeze
|
4
|
+
LAT_RANGE = (-85.05112878..85.05112878).freeze
|
5
|
+
STEP = 26
|
6
|
+
UNITS = {
|
7
|
+
m: 1,
|
8
|
+
km: 1000,
|
9
|
+
ft: 0.3048,
|
10
|
+
mi: 1609.34
|
11
|
+
}.freeze
|
12
|
+
D_R = Math::PI / 180.0
|
13
|
+
EARTH_RADIUS_IN_METERS = 6_372_797.560856
|
14
|
+
|
15
|
+
ruby2_keywords def geoadd(key, *args)
|
16
|
+
points = parse_points(args)
|
17
|
+
|
18
|
+
scored_points = points.map do |point|
|
19
|
+
score = geohash_encode(point[:lng], point[:lat])[:bits]
|
20
|
+
[score.to_s, point[:key]]
|
21
|
+
end
|
22
|
+
|
23
|
+
zadd(key, scored_points)
|
24
|
+
end
|
25
|
+
|
26
|
+
def geodist(key, member1, member2, unit = 'm')
|
27
|
+
to_meter = parse_unit(unit)
|
28
|
+
|
29
|
+
return nil if zcard(key).zero?
|
30
|
+
|
31
|
+
score1 = zscore(key, member1)
|
32
|
+
score2 = zscore(key, member2)
|
33
|
+
return nil if score1.nil? || score2.nil?
|
34
|
+
hash1 = { bits: score1.to_i, step: STEP }
|
35
|
+
hash2 = { bits: score2.to_i, step: STEP }
|
36
|
+
|
37
|
+
lng1, lat1 = geohash_decode(hash1)
|
38
|
+
lng2, lat2 = geohash_decode(hash2)
|
39
|
+
|
40
|
+
distance = geohash_distance(lng1, lat1, lng2, lat2) / to_meter
|
41
|
+
format('%<distance>.4f', distance: distance)
|
42
|
+
end
|
43
|
+
|
44
|
+
def geohash(key, members)
|
45
|
+
lng_range = (-180..180)
|
46
|
+
lat_range = (-90..90)
|
47
|
+
geoalphabet = '0123456789bcdefghjkmnpqrstuvwxyz'
|
48
|
+
|
49
|
+
Array(members).map do |member|
|
50
|
+
score = zscore(key, member)
|
51
|
+
next nil unless score
|
52
|
+
score = score.to_i
|
53
|
+
hash = { bits: score, step: STEP }
|
54
|
+
lng, lat = geohash_decode(hash)
|
55
|
+
bits = geohash_encode(lng, lat, lng_range, lat_range)[:bits]
|
56
|
+
hash = ''
|
57
|
+
11.times do |i|
|
58
|
+
shift = (52 - ((i + 1) * 5))
|
59
|
+
idx = shift > 0 ? (bits >> shift) & 0x1f : 0
|
60
|
+
hash << geoalphabet[idx]
|
61
|
+
end
|
62
|
+
hash
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def geopos(key, members)
|
67
|
+
Array(members).map do |member|
|
68
|
+
score = zscore(key, member)
|
69
|
+
next nil unless score
|
70
|
+
hash = { bits: score.to_i, step: STEP }
|
71
|
+
lng, lat = geohash_decode(hash)
|
72
|
+
lng = format_decoded_coord(lng)
|
73
|
+
lat = format_decoded_coord(lat)
|
74
|
+
[lng, lat]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def parse_points(args)
|
81
|
+
points = args.each_slice(3).to_a
|
82
|
+
|
83
|
+
if points.last.size != 3
|
84
|
+
raise Redis::CommandError,
|
85
|
+
"ERR wrong number of arguments for 'geoadd' command"
|
86
|
+
end
|
87
|
+
|
88
|
+
points.map do |point|
|
89
|
+
parse_point(point)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_point(point)
|
94
|
+
lng = Float(point[0])
|
95
|
+
lat = Float(point[1])
|
96
|
+
|
97
|
+
unless LNG_RANGE.include?(lng) && LAT_RANGE.include?(lat)
|
98
|
+
lng = format('%<long>.6f', long: lng)
|
99
|
+
lat = format('%<lat>.6f', lat: lat)
|
100
|
+
raise Redis::CommandError,
|
101
|
+
"ERR invalid longitude,latitude pair #{lng},#{lat}"
|
102
|
+
end
|
103
|
+
|
104
|
+
{ key: point[2], lng: lng, lat: lat }
|
105
|
+
rescue ArgumentError
|
106
|
+
raise Redis::CommandError, 'ERR value is not a valid float'
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns ZSET score for passed coordinates
|
110
|
+
def geohash_encode(lng, lat, lng_range = LNG_RANGE, lat_range = LAT_RANGE, step = STEP)
|
111
|
+
lat_offset = (lat - lat_range.min) / (lat_range.max - lat_range.min)
|
112
|
+
lng_offset = (lng - lng_range.min) / (lng_range.max - lng_range.min)
|
113
|
+
|
114
|
+
lat_offset *= (1 << step)
|
115
|
+
lng_offset *= (1 << step)
|
116
|
+
|
117
|
+
bits = interleave(lat_offset.to_i, lng_offset.to_i)
|
118
|
+
|
119
|
+
{ bits: bits, step: step }
|
120
|
+
end
|
121
|
+
|
122
|
+
def interleave(x, y)
|
123
|
+
b = [0x5555555555555555, 0x3333333333333333, 0x0F0F0F0F0F0F0F0F,
|
124
|
+
0x00FF00FF00FF00FF, 0x0000FFFF0000FFFF]
|
125
|
+
s = [1, 2, 4, 8, 16]
|
126
|
+
|
127
|
+
x = (x | (x << s[4])) & b[4]
|
128
|
+
y = (y | (y << s[4])) & b[4]
|
129
|
+
|
130
|
+
x = (x | (x << s[3])) & b[3]
|
131
|
+
y = (y | (y << s[3])) & b[3]
|
132
|
+
|
133
|
+
x = (x | (x << s[2])) & b[2]
|
134
|
+
y = (y | (y << s[2])) & b[2]
|
135
|
+
|
136
|
+
x = (x | (x << s[1])) & b[1]
|
137
|
+
y = (y | (y << s[1])) & b[1]
|
138
|
+
|
139
|
+
x = (x | (x << s[0])) & b[0]
|
140
|
+
y = (y | (y << s[0])) & b[0]
|
141
|
+
|
142
|
+
x | (y << 1)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Decodes ZSET score to coordinates pair
|
146
|
+
def geohash_decode(hash, lng_range = LNG_RANGE, lat_range = LAT_RANGE)
|
147
|
+
area = calculate_approximate_area(hash, lng_range, lat_range)
|
148
|
+
|
149
|
+
lng = (area[:lng_min] + area[:lng_max]) / 2
|
150
|
+
lat = (area[:lat_min] + area[:lat_max]) / 2
|
151
|
+
|
152
|
+
[lng, lat]
|
153
|
+
end
|
154
|
+
|
155
|
+
def calculate_approximate_area(hash, lng_range, lat_range)
|
156
|
+
bits = hash[:bits]
|
157
|
+
step = hash[:step]
|
158
|
+
hash_sep = deinterleave(bits)
|
159
|
+
|
160
|
+
lat_scale = lat_range.max - lat_range.min
|
161
|
+
lng_scale = lng_range.max - lng_range.min
|
162
|
+
|
163
|
+
ilato = hash_sep & 0xFFFFFFFF # cast int64 to int32 to get lat part of deinterleaved hash
|
164
|
+
ilngo = hash_sep >> 32 # shift over to get lng part of hash
|
165
|
+
|
166
|
+
{
|
167
|
+
lat_min: lat_range.min + (ilato * 1.0 / (1 << step)) * lat_scale,
|
168
|
+
lat_max: lat_range.min + ((ilato + 1) * 1.0 / (1 << step)) * lat_scale,
|
169
|
+
lng_min: lng_range.min + (ilngo * 1.0 / (1 << step)) * lng_scale,
|
170
|
+
lng_max: lng_range.min + ((ilngo + 1) * 1.0 / (1 << step)) * lng_scale
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
def deinterleave(bits)
|
175
|
+
b = [0x5555555555555555, 0x3333333333333333, 0x0F0F0F0F0F0F0F0F,
|
176
|
+
0x00FF00FF00FF00FF, 0x0000FFFF0000FFFF, 0x00000000FFFFFFFF]
|
177
|
+
s = [0, 1, 2, 4, 8, 16]
|
178
|
+
|
179
|
+
x = bits
|
180
|
+
y = bits >> 1
|
181
|
+
|
182
|
+
x = (x | (x >> s[0])) & b[0]
|
183
|
+
y = (y | (y >> s[0])) & b[0]
|
184
|
+
|
185
|
+
x = (x | (x >> s[1])) & b[1]
|
186
|
+
y = (y | (y >> s[1])) & b[1]
|
187
|
+
|
188
|
+
x = (x | (x >> s[2])) & b[2]
|
189
|
+
y = (y | (y >> s[2])) & b[2]
|
190
|
+
|
191
|
+
x = (x | (x >> s[3])) & b[3]
|
192
|
+
y = (y | (y >> s[3])) & b[3]
|
193
|
+
|
194
|
+
x = (x | (x >> s[4])) & b[4]
|
195
|
+
y = (y | (y >> s[4])) & b[4]
|
196
|
+
|
197
|
+
x = (x | (x >> s[5])) & b[5]
|
198
|
+
y = (y | (y >> s[5])) & b[5]
|
199
|
+
|
200
|
+
x | (y << 32)
|
201
|
+
end
|
202
|
+
|
203
|
+
def format_decoded_coord(coord)
|
204
|
+
coord = format('%<coord>.17f', coord: coord)
|
205
|
+
l = 1
|
206
|
+
l += 1 while coord[-l] == '0'
|
207
|
+
coord = coord[0..-l]
|
208
|
+
coord[-1] == '.' ? coord[0..-2] : coord
|
209
|
+
end
|
210
|
+
|
211
|
+
def parse_unit(unit)
|
212
|
+
unit = unit.to_sym
|
213
|
+
return UNITS[unit] if UNITS[unit]
|
214
|
+
|
215
|
+
raise Redis::CommandError,
|
216
|
+
'ERR unsupported unit provided. please use m, km, ft, mi'
|
217
|
+
end
|
218
|
+
|
219
|
+
def geohash_distance(lng1d, lat1d, lng2d, lat2d)
|
220
|
+
lat1r = deg_rad(lat1d)
|
221
|
+
lng1r = deg_rad(lng1d)
|
222
|
+
lat2r = deg_rad(lat2d)
|
223
|
+
lng2r = deg_rad(lng2d)
|
224
|
+
|
225
|
+
u = Math.sin((lat2r - lat1r) / 2)
|
226
|
+
v = Math.sin((lng2r - lng1r) / 2)
|
227
|
+
|
228
|
+
2.0 * EARTH_RADIUS_IN_METERS *
|
229
|
+
Math.asin(Math.sqrt(u * u + Math.cos(lat1r) * Math.cos(lat2r) * v * v))
|
230
|
+
end
|
231
|
+
|
232
|
+
def deg_rad(ang)
|
233
|
+
ang * D_R
|
234
|
+
end
|
235
|
+
|
236
|
+
def rad_deg(ang)
|
237
|
+
ang / D_R
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -6,41 +6,61 @@ class MockRedis
|
|
6
6
|
include Assertions
|
7
7
|
include UtilityMethods
|
8
8
|
|
9
|
-
def hdel(key,
|
9
|
+
def hdel(key, *fields)
|
10
10
|
with_hash_at(key) do |hash|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
hash.delete(field.to_s) ? 1 : 0
|
11
|
+
orig_size = hash.size
|
12
|
+
fields = Array(fields).flatten.map(&:to_s)
|
13
|
+
|
14
|
+
if fields.empty?
|
15
|
+
raise Redis::CommandError, "ERR wrong number of arguments for 'hdel' command"
|
17
16
|
end
|
17
|
+
|
18
|
+
hash.delete_if { |k, _v| fields.include?(k) }
|
19
|
+
orig_size - hash.size
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
23
|
def hexists(key, field)
|
22
|
-
with_hash_at(key) {|h| h.
|
24
|
+
with_hash_at(key) { |h| h.key?(field.to_s) }
|
23
25
|
end
|
24
26
|
|
25
27
|
def hget(key, field)
|
26
|
-
with_hash_at(key) {|h| h[field.to_s]}
|
28
|
+
with_hash_at(key) { |h| h[field.to_s] }
|
27
29
|
end
|
28
30
|
|
29
31
|
def hgetall(key)
|
30
|
-
with_hash_at(key) {|h| h}
|
32
|
+
with_hash_at(key) { |h| h }
|
31
33
|
end
|
32
34
|
|
33
35
|
def hincrby(key, field, increment)
|
34
36
|
with_hash_at(key) do |hash|
|
35
37
|
field = field.to_s
|
36
38
|
unless can_incr?(data[key][field])
|
37
|
-
raise Redis::CommandError,
|
39
|
+
raise Redis::CommandError, 'ERR hash value is not an integer'
|
38
40
|
end
|
39
41
|
unless looks_like_integer?(increment.to_s)
|
40
|
-
raise Redis::CommandError,
|
42
|
+
raise Redis::CommandError, 'ERR value is not an integer or out of range'
|
41
43
|
end
|
42
44
|
|
43
|
-
new_value = (hash[field] ||
|
45
|
+
new_value = (hash[field] || '0').to_i + increment.to_i
|
46
|
+
hash[field] = new_value.to_s
|
47
|
+
new_value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def hincrbyfloat(key, field, increment)
|
52
|
+
with_hash_at(key) do |hash|
|
53
|
+
field = field.to_s
|
54
|
+
unless can_incr_float?(data[key][field])
|
55
|
+
raise Redis::CommandError, 'ERR hash value is not a float'
|
56
|
+
end
|
57
|
+
|
58
|
+
unless looks_like_float?(increment.to_s)
|
59
|
+
raise Redis::CommandError, 'ERR value is not a valid float'
|
60
|
+
end
|
61
|
+
|
62
|
+
new_value = (hash[field] || '0').to_f + increment.to_f
|
63
|
+
new_value = new_value.to_i if new_value % 1 == 0
|
44
64
|
hash[field] = new_value.to_s
|
45
65
|
new_value
|
46
66
|
end
|
@@ -55,22 +75,36 @@ class MockRedis
|
|
55
75
|
end
|
56
76
|
|
57
77
|
def hmget(key, *fields)
|
78
|
+
fields.flatten!
|
79
|
+
|
58
80
|
assert_has_args(fields, 'hmget')
|
59
|
-
fields.map{|f| hget(key, f)}
|
81
|
+
fields.map { |f| hget(key, f) }
|
60
82
|
end
|
61
83
|
|
62
84
|
def mapped_hmget(key, *fields)
|
63
85
|
reply = hmget(key, *fields)
|
64
|
-
|
86
|
+
if reply.is_a?(Array)
|
87
|
+
Hash[fields.zip(reply)]
|
88
|
+
else
|
89
|
+
reply
|
90
|
+
end
|
65
91
|
end
|
66
92
|
|
67
93
|
def hmset(key, *kvpairs)
|
94
|
+
if key.is_a? Array
|
95
|
+
err_msg = 'ERR wrong number of arguments for \'hmset\' command'
|
96
|
+
kvpairs = key[1..-1]
|
97
|
+
key = key[0]
|
98
|
+
end
|
99
|
+
|
100
|
+
kvpairs.flatten!
|
68
101
|
assert_has_args(kvpairs, 'hmset')
|
102
|
+
|
69
103
|
if kvpairs.length.odd?
|
70
|
-
raise Redis::CommandError,
|
104
|
+
raise Redis::CommandError, err_msg || 'ERR wrong number of arguments for \'hmset\' command'
|
71
105
|
end
|
72
106
|
|
73
|
-
kvpairs.each_slice(2) do |(k,v)|
|
107
|
+
kvpairs.each_slice(2) do |(k, v)|
|
74
108
|
hset(key, k, v)
|
75
109
|
end
|
76
110
|
'OK'
|
@@ -86,9 +120,34 @@ class MockRedis
|
|
86
120
|
hmset(key, *kvpairs)
|
87
121
|
end
|
88
122
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
123
|
+
def hscan(key, cursor, opts = {})
|
124
|
+
opts = opts.merge(key: lambda { |x| x[0] })
|
125
|
+
common_scan(hgetall(key).to_a, cursor, opts)
|
126
|
+
end
|
127
|
+
|
128
|
+
def hscan_each(key, opts = {}, &block)
|
129
|
+
return to_enum(:hscan_each, key, opts) unless block_given?
|
130
|
+
cursor = 0
|
131
|
+
loop do
|
132
|
+
cursor, values = hscan(key, cursor, opts)
|
133
|
+
values.each(&block)
|
134
|
+
break if cursor == '0'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def hset(key, *args)
|
139
|
+
added = 0
|
140
|
+
with_hash_at(key) do |hash|
|
141
|
+
if args.length == 1 && args[0].is_a?(Hash)
|
142
|
+
args = args[0].to_a.flatten
|
143
|
+
end
|
144
|
+
|
145
|
+
args.each_slice(2) do |field, value|
|
146
|
+
added += 1 unless hash.key?(field.to_s)
|
147
|
+
hash[field.to_s] = value.to_s
|
148
|
+
end
|
149
|
+
end
|
150
|
+
added
|
92
151
|
end
|
93
152
|
|
94
153
|
def hsetnx(key, field, value)
|
@@ -107,18 +166,18 @@ class MockRedis
|
|
107
166
|
private
|
108
167
|
|
109
168
|
def with_hash_at(key, &blk)
|
110
|
-
with_thing_at(key, :assert_hashy, proc {{}}, &blk)
|
169
|
+
with_thing_at(key.to_s, :assert_hashy, proc { {} }, &blk)
|
111
170
|
end
|
112
171
|
|
113
172
|
def hashy?(key)
|
114
|
-
data[key].nil? || data[key].
|
173
|
+
data[key].nil? || data[key].is_a?(Hash)
|
115
174
|
end
|
116
175
|
|
117
176
|
def assert_hashy(key)
|
118
177
|
unless hashy?(key)
|
119
|
-
raise Redis::CommandError,
|
178
|
+
raise Redis::CommandError,
|
179
|
+
'WRONGTYPE Operation against a key holding the wrong kind of value'
|
120
180
|
end
|
121
181
|
end
|
122
|
-
|
123
182
|
end
|
124
183
|
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
class MockRedis
|
2
|
+
module InfoMethod
|
3
|
+
SERVER_INFO = {
|
4
|
+
'arch_bits' => '64',
|
5
|
+
'gcc_version' => '4.2.1',
|
6
|
+
'lru_clock' => '1036434',
|
7
|
+
'multiplexing_api' => 'kqueue',
|
8
|
+
'os' => 'Darwin 12.2.1 x86_64',
|
9
|
+
'process_id' => '14508',
|
10
|
+
'redis_git_dirty' => '0',
|
11
|
+
'redis_git_sha1' => '00000000',
|
12
|
+
'redis_mode' => 'standalone',
|
13
|
+
'redis_version' => '2.6.10',
|
14
|
+
'run_id' => '1f3f7af2fc11eb68011eb43e154adce125c85e9a',
|
15
|
+
'tcp_port' => '6379',
|
16
|
+
'uptime_in_days' => '0',
|
17
|
+
'uptime_in_seconds' => '84215',
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
CLIENTS_INFO = {
|
21
|
+
'blocked_clients' => '0',
|
22
|
+
'client_biggest_input_buf' => '0',
|
23
|
+
'client_longest_output_list' => '0',
|
24
|
+
'connected_clients' => '1',
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
MEMORY_INFO = {
|
28
|
+
'mem_allocator' => 'libc',
|
29
|
+
'mem_fragmentation_ratio' => '2.04',
|
30
|
+
'used_memory' => '931456',
|
31
|
+
'used_memory_human' => '909.62K',
|
32
|
+
'used_memory_lua' => '31744',
|
33
|
+
'used_memory_peak' => '1005424',
|
34
|
+
'used_memory_peak_human' => '981.86K',
|
35
|
+
'used_memory_rss' => '1904640',
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
PERSISTENCE_INFO = {
|
39
|
+
'aof_current_rewrite_time_sec' => '-1',
|
40
|
+
'aof_enabled' => '0',
|
41
|
+
'aof_last_bgrewrite_status' => 'ok',
|
42
|
+
'aof_last_rewrite_time_sec' => '-1',
|
43
|
+
'aof_rewrite_in_progress' => '0',
|
44
|
+
'aof_rewrite_scheduled' => '0',
|
45
|
+
'loading' => '0',
|
46
|
+
'rdb_bgsave_in_progress' => '0',
|
47
|
+
'rdb_changes_since_last_save' => '0',
|
48
|
+
'rdb_current_bgsave_time_sec' => '-1',
|
49
|
+
'rdb_last_bgsave_status' => 'ok',
|
50
|
+
'rdb_last_bgsave_time_sec' => '-1',
|
51
|
+
'rdb_last_save_time' => '1361423635',
|
52
|
+
}.freeze
|
53
|
+
|
54
|
+
STATS_INFO = {
|
55
|
+
'evicted_keys' => '0',
|
56
|
+
'expired_keys' => '0',
|
57
|
+
'instantaneous_ops_per_sec' => '0',
|
58
|
+
'keyspace_hits' => '62645',
|
59
|
+
'keyspace_misses' => '29757',
|
60
|
+
'latest_fork_usec' => '0',
|
61
|
+
'pubsub_channels' => '0',
|
62
|
+
'pubsub_patterns' => '0',
|
63
|
+
'rejected_connections' => '0',
|
64
|
+
'total_commands_processed' => '196800',
|
65
|
+
'total_connections_received' => '4359',
|
66
|
+
}.freeze
|
67
|
+
|
68
|
+
REPLICATION_INFO = {
|
69
|
+
'role' => 'master',
|
70
|
+
'connected_slaves' => '0',
|
71
|
+
}.freeze
|
72
|
+
|
73
|
+
CPU_INFO = {
|
74
|
+
'used_cpu_sys' => '5.54',
|
75
|
+
'used_cpu_sys_childrens' => '0.00',
|
76
|
+
'used_cpu_user' => '7.65',
|
77
|
+
'used_cpu_user_childrens' => '0.02',
|
78
|
+
}.freeze
|
79
|
+
|
80
|
+
KEYSPACE_INFO = {
|
81
|
+
'db0' => 'keys=8,expires=0',
|
82
|
+
}.freeze
|
83
|
+
|
84
|
+
# The Ruby Redis client returns commandstats differently when it's called as
|
85
|
+
# "INFO commandstats".
|
86
|
+
# rubocop:disable Layout/LineLength
|
87
|
+
COMMAND_STATS_SOLO_INFO = {
|
88
|
+
'auth' => { 'calls' => '572501', 'usec' => '2353163', 'usec_per_call' => '4.11' },
|
89
|
+
'client' => { 'calls' => '1', 'usec' => '80', 'usec_per_call' => '80.00' },
|
90
|
+
'config' => { 'calls' => '4', 'usec' => '71', 'usec_per_call' => '17.75' },
|
91
|
+
'flushall' => { 'calls' => '2097790', 'usec' => '27473940655', 'usec_per_call' => '13096.61' },
|
92
|
+
'flushdb' => { 'calls' => '692247', 'usec' => '2235143397', 'usec_per_call' => '3228.82' },
|
93
|
+
'get' => { 'calls' => '185724', 'usec' => '604479', 'usec_per_call' => '3.25' },
|
94
|
+
'info' => { 'calls' => '965566', 'usec' => '229377331', 'usec_per_call' => '237.56' },
|
95
|
+
'keys' => { 'calls' => '16591', 'usec' => '917676', 'usec_per_call' => '55.31' },
|
96
|
+
'llen' => { 'calls' => '39', 'usec' => '150', 'usec_per_call' => '3.85' },
|
97
|
+
'ping' => { 'calls' => '697509', 'usec' => '3836044', 'usec_per_call' => '5.50' },
|
98
|
+
'rpush' => { 'calls' => '2239810', 'usec' => '15013298', 'usec_per_call' => '6.70' },
|
99
|
+
'sadd' => { 'calls' => '13950129156', 'usec' => '30126628979', 'usec_per_call' => '2.16' },
|
100
|
+
'scard' => { 'calls' => '153929', 'usec' => '726596', 'usec_per_call' => '4.72' },
|
101
|
+
'set' => { 'calls' => '6975081982', 'usec' => '16453671451', 'usec_per_call' => '2.36' },
|
102
|
+
'slowlog' => { 'calls' => '136', 'usec' => '16815', 'usec_per_call' => '123.64' },
|
103
|
+
'smembers' => { 'calls' => '58', 'usec' => '231', 'usec_per_call' => '3.98' },
|
104
|
+
'sunionstore' => { 'calls' => '4185027', 'usec' => '11762454022', 'usec_per_call' => '2810.60' },
|
105
|
+
}.freeze
|
106
|
+
|
107
|
+
COMMAND_STATS_COMBINED_INFO = {
|
108
|
+
'cmdstat_auth' => 'calls=572506,usec=2353182,usec_per_call=4.11',
|
109
|
+
'cmdstat_client' => 'calls=1,usec=80,usec_per_call=80.00',
|
110
|
+
'cmdstat_config' => 'calls=4,usec=71,usec_per_call=17.75',
|
111
|
+
'cmdstat_flushall' => 'calls=2097790,usec=27473940655,usec_per_call=13096.61',
|
112
|
+
'cmdstat_flushdb' => 'calls=692247,usec=2235143397,usec_per_call=3228.82',
|
113
|
+
'cmdstat_get' => 'calls=185724,usec=604479,usec_per_call=3.25',
|
114
|
+
'cmdstat_info' => 'calls=965571,usec=229378327,usec_per_call=237.56',
|
115
|
+
'cmdstat_keys' => 'calls=16591,usec=917676,usec_per_call=55.31',
|
116
|
+
'cmdstat_llen' => 'calls=39,usec=150,usec_per_call=3.85',
|
117
|
+
'cmdstat_ping' => 'calls=697509,usec=3836044,usec_per_call=5.50',
|
118
|
+
'cmdstat_rpush' => 'calls=2239810,usec=15013298,usec_per_call=6.70',
|
119
|
+
'cmdstat_sadd' => 'calls=13950129156,usec=30126628979,usec_per_call=2.16',
|
120
|
+
'cmdstat_scard' => 'calls=153929,usec=726596,usec_per_call=4.72',
|
121
|
+
'cmdstat_set' => 'calls=6975081982,usec=16453671451,usec_per_call=2.36',
|
122
|
+
'cmdstat_slowlog' => 'calls=136,usec=16815,usec_per_call=123.64',
|
123
|
+
'cmdstat_smembers' => 'calls=58,usec=231,usec_per_call=3.98',
|
124
|
+
'cmdstat_sunionstore' => 'calls=4185027,usec=11762454022,usec_per_call=2810.60',
|
125
|
+
}.freeze
|
126
|
+
# rubocop:enable Layout/LineLength
|
127
|
+
|
128
|
+
DEFAULT_INFO = [
|
129
|
+
SERVER_INFO,
|
130
|
+
CLIENTS_INFO,
|
131
|
+
MEMORY_INFO,
|
132
|
+
PERSISTENCE_INFO,
|
133
|
+
STATS_INFO,
|
134
|
+
REPLICATION_INFO,
|
135
|
+
CPU_INFO,
|
136
|
+
KEYSPACE_INFO,
|
137
|
+
].inject({}) { |memo, info| memo.merge(info) }
|
138
|
+
|
139
|
+
ALL_INFO = [
|
140
|
+
DEFAULT_INFO,
|
141
|
+
COMMAND_STATS_COMBINED_INFO,
|
142
|
+
].inject({}) { |memo, info| memo.merge(info) }
|
143
|
+
|
144
|
+
def info(section = :default)
|
145
|
+
case section
|
146
|
+
when :default; DEFAULT_INFO
|
147
|
+
when :all; ALL_INFO
|
148
|
+
when :server; SERVER_INFO
|
149
|
+
when :clients; CLIENTS_INFO
|
150
|
+
when :memory; MEMORY_INFO
|
151
|
+
when :persistence; PERSISTENCE_INFO
|
152
|
+
when :stats; STATS_INFO
|
153
|
+
when :replication; REPLICATION_INFO
|
154
|
+
when :cpu; CPU_INFO
|
155
|
+
when :keyspace; KEYSPACE_INFO
|
156
|
+
when :commandstats; COMMAND_STATS_SOLO_INFO
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|