redis_migrator 0.0.1

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.
@@ -0,0 +1,432 @@
1
+ require 'mock_redis/assertions'
2
+ require 'mock_redis/exceptions'
3
+ require 'mock_redis/hash_methods'
4
+ require 'mock_redis/list_methods'
5
+ require 'mock_redis/set_methods'
6
+ require 'mock_redis/string_methods'
7
+ require 'mock_redis/zset_methods'
8
+
9
+ class MockRedis
10
+ class Database
11
+ include HashMethods
12
+ include ListMethods
13
+ include SetMethods
14
+ include StringMethods
15
+ include ZsetMethods
16
+
17
+ attr_reader :data, :expire_times
18
+
19
+ def initialize(*args)
20
+ @data = {}
21
+ @expire_times = []
22
+ end
23
+
24
+ def initialize_copy(source)
25
+ @data = @data.clone
26
+ @data.keys.each {|k| @data[k] = @data[k].clone}
27
+ @expire_times = @expire_times.map{|x| x.clone}
28
+ end
29
+
30
+ # Redis commands go below this line and above 'private'
31
+
32
+ def auth(_) 'OK' end
33
+
34
+ def bgrewriteaof() "Background append only file rewriting started" end
35
+
36
+ def bgsave() "Background saving started" end
37
+
38
+ def dbsize
39
+ data.keys.length
40
+ end
41
+
42
+ def del(*keys)
43
+ keys.
44
+ find_all{|key| data[key]}.
45
+ each {|k| persist(k)}.
46
+ each {|k| data.delete(k)}.
47
+ length
48
+ end
49
+
50
+ def echo(msg)
51
+ msg.to_s
52
+ end
53
+
54
+ def expire(key, seconds)
55
+ expireat(key, Time.now.to_i + seconds.to_i)
56
+ end
57
+
58
+ def expireat(key, timestamp)
59
+ unless looks_like_integer?(timestamp.to_s)
60
+ raise RuntimeError, "ERR value is not an integer or out of range"
61
+ end
62
+
63
+ if exists(key)
64
+ set_expiration(key, Time.at(timestamp.to_i))
65
+ true
66
+ else
67
+ false
68
+ end
69
+ end
70
+
71
+ def exists(key)
72
+ data.has_key?(key)
73
+ end
74
+
75
+ def flushdb
76
+ data.keys.each {|k| del(k)}
77
+ 'OK'
78
+ end
79
+
80
+ def info
81
+ astats = [
82
+ ["2", "2699"],
83
+ ["6", "1"],
84
+ ["7", "1"],
85
+ ["8", "17197"],
86
+ ["9", "109875"],
87
+ ["10", "94348"],
88
+ ["11", "32580"],
89
+ ["12", "52347"],
90
+ ["13", "86475"],
91
+ ["14", "58175"],
92
+ ["15", "53408"],
93
+ ["16", "876949"],
94
+ ["17", "71157"],
95
+ ["18", "5104"],
96
+ ["19", "2705"],
97
+ ["20", "2903"],
98
+ ["21", "1024"],
99
+ ["22", "2546"],
100
+ ["23", "952"],
101
+ ["24", "186080"],
102
+ ["25", "611"],
103
+ ["26", "40936"],
104
+ ["27", "960"],
105
+ ["28", "1323"],
106
+ ["29", "14216"],
107
+ ["30", "52412"],
108
+ ["31", "21130"],
109
+ ["32", "47959"],
110
+ ["33", "6891"],
111
+ ["34", "9712"],
112
+ ["35", "3366"],
113
+ ["36", "5737"],
114
+ ["37", "11274"],
115
+ ["38", "8057"],
116
+ ["39", "2957"],
117
+ ["40", "51200"],
118
+ ["42", "8220"],
119
+ ["43", "8278"],
120
+ ["44", "6539"],
121
+ ["45", "764"],
122
+ ["47", "1018"],
123
+ ["48", "19250"],
124
+ ["49", "713"],
125
+ ["51", "51"],
126
+ ["53", "2"],
127
+ ["55", "3922"],
128
+ ["56", "153"],
129
+ ["57", "614"],
130
+ ["58", "1"],
131
+ ["59", "1775"],
132
+ ["61", "32865"],
133
+ ["63", "2530"],
134
+ ["64", "565"],
135
+ ["65", "1322"],
136
+ ["67", "1572"],
137
+ ["69", "1421"],
138
+ ["71", "1220"],
139
+ ["72", "241"],
140
+ ["73", "5432"],
141
+ ["74", "1122"],
142
+ ["75", "2555"],
143
+ ["77", "1539"],
144
+ ["78", "612"],
145
+ ["79", "902"],
146
+ ["81", "1678"],
147
+ ["83", "51"],
148
+ ["84", "612"],
149
+ ["85", "706"],
150
+ ["87", "410"],
151
+ ["88", "5435"],
152
+ ["89", "813"],
153
+ ["90", "612"],
154
+ ["93", "153"],
155
+ ["94", "612"],
156
+ ["96", "159"],
157
+ ["97", "306"],
158
+ ["99", "153"],
159
+ ["101", "456"],
160
+ ["103", "741"],
161
+ ["105", "447"],
162
+ ["107", "754"],
163
+ ["109", "414"],
164
+ ["111", "475"],
165
+ ["113", "757"],
166
+ ["115", "287"],
167
+ ["117", "420"],
168
+ ["118", "765"],
169
+ ["119", "642"],
170
+ ["120", "159"],
171
+ ["121", "926"],
172
+ ["122", "612"],
173
+ ["123", "251"],
174
+ ["125", "390"],
175
+ ["127", "354"],
176
+ ["128", "617"],
177
+ ["129", "528"],
178
+ ["131", "298"],
179
+ ["132", "612"],
180
+ ["133", "809"],
181
+ ["135", "244"],
182
+ ["136", "306"],
183
+ ["137", "504"],
184
+ ["139", "201"],
185
+ ["141", "1124"],
186
+ ["143", "139"],
187
+ ["144", "159"],
188
+ ["145", "1322"],
189
+ ["147", "410"],
190
+ ["149", "253"],
191
+ ["151", "304"],
192
+ ["153", "312"],
193
+ ["155", "249"],
194
+ ["157", "306"],
195
+ ["159", "348"],
196
+ ["161", "255"],
197
+ ["163", "458"],
198
+ ["165", "5"],
199
+ ["167", "306"],
200
+ ["168", "47"],
201
+ ["169", "214"],
202
+ ["171", "250"],
203
+ ["173", "5"],
204
+ ["177", "10"],
205
+ ["179", "158"],
206
+ ["181", "5"],
207
+ ["183", "10"],
208
+ ["185", "51"],
209
+ ["187", "49"],
210
+ ["191", "5"],
211
+ ["192", "47"],
212
+ ["193", "51"],
213
+ ["197", "112"],
214
+ ["199", "5"],
215
+ ["201", "5"],
216
+ ["203", "5"],
217
+ ["209", "5"],
218
+ ["213", "51"],
219
+ ["217", "102"],
220
+ ["225", "357"],
221
+ ["229", "51"],
222
+ ["233", "204"],
223
+ ["237", "51"],
224
+ ["239", "1"],
225
+ ["247", "46"],
226
+ ["255", "102"],
227
+ [">=256", "6201"],
228
+ ]
229
+
230
+ {
231
+ "allocation_stats" => astats.map {|(a,b)| "#{a}=#{b}"}.join(','),
232
+ "aof_enabled" => "0",
233
+ "arch_bits" => "64",
234
+ "bgrewriteaof_in_progress" => "0",
235
+ "bgsave_in_progress" => "0",
236
+ "blocked_clients" => "0",
237
+ "changes_since_last_save" => "0",
238
+ "client_biggest_input_buf" => "0",
239
+ "client_longest_output_list" => "0",
240
+ "connected_clients" => "1",
241
+ "connected_slaves" => "0",
242
+ "db0" => "keys=8,expires=0",
243
+ "evicted_keys" => "0",
244
+ "expired_keys" => "0",
245
+ "hash_max_zipmap_entries" => "512",
246
+ "hash_max_zipmap_value" => "64",
247
+ "keyspace_hits" => "62645",
248
+ "keyspace_misses" => "29757",
249
+ "last_save_time" => "1310596333",
250
+ "loading" => "0",
251
+ "lru_clock" => "1036434",
252
+ "mem_fragmentation_ratio" => "2.04",
253
+ "multiplexing_api" => "kqueue",
254
+ "process_id" => "14508",
255
+ "pubsub_channels" => "0",
256
+ "pubsub_patterns" => "0",
257
+ "redis_git_dirty" => "0",
258
+ "redis_git_sha1" => "00000000",
259
+ "redis_version" => "2.2.11",
260
+ "role" => "master",
261
+ "total_commands_processed" => "196800",
262
+ "total_connections_received" => "4359",
263
+ "uptime_in_days" => "0",
264
+ "uptime_in_seconds" => "84215",
265
+ "use_tcmalloc" => "0",
266
+ "used_cpu_sys" => "5.54",
267
+ "used_cpu_sys_childrens" => "0.00",
268
+ "used_cpu_user" => "7.65",
269
+ "used_cpu_user_childrens" => "0.02",
270
+ "used_memory" => "931456",
271
+ "used_memory_human" => "909.62K",
272
+ "used_memory_rss" => "1904640",
273
+ "vm_enabled" => "0",
274
+ }
275
+ end
276
+
277
+ def keys(format)
278
+ data.keys.grep(redis_pattern_to_ruby_regex(format))
279
+ end
280
+
281
+ def lastsave
282
+ Time.now.to_i
283
+ end
284
+
285
+ def persist(key)
286
+ if exists(key) && has_expiration?(key)
287
+ remove_expiration(key)
288
+ true
289
+ else
290
+ false
291
+ end
292
+ end
293
+
294
+ def ping
295
+ 'PONG'
296
+ end
297
+
298
+ def quit
299
+ 'OK'
300
+ end
301
+
302
+ def randomkey
303
+ data.keys[rand(data.length)]
304
+ end
305
+
306
+ def rename(key, newkey)
307
+ if key == newkey
308
+ raise RuntimeError, "ERR source and destination objects are the same"
309
+ end
310
+ data[newkey] = data.delete(key)
311
+ 'OK'
312
+ end
313
+
314
+ def renamenx(key, newkey)
315
+ if key == newkey
316
+ raise RuntimeError, "ERR source and destination objects are the same"
317
+ end
318
+ if exists(newkey)
319
+ false
320
+ else
321
+ rename(key, newkey)
322
+ true
323
+ end
324
+ end
325
+
326
+ def save
327
+ 'OK'
328
+ end
329
+
330
+ def ttl(key)
331
+ if has_expiration?(key)
332
+ expiration(key).to_i - Time.now.to_i
333
+ else
334
+ -1
335
+ end
336
+ end
337
+
338
+ def type(key)
339
+ if !exists(key)
340
+ 'none'
341
+ elsif hashy?(key)
342
+ 'hash'
343
+ elsif stringy?(key)
344
+ 'string'
345
+ elsif listy?(key)
346
+ 'list'
347
+ elsif sety?(key)
348
+ 'set'
349
+ elsif zsety?(key)
350
+ 'zset'
351
+ else
352
+ raise ArgumentError, "Not sure how #{data[key].inspect} got in here"
353
+ end
354
+ end
355
+
356
+ private
357
+
358
+ def assert_valid_timeout(timeout)
359
+ if !looks_like_integer?(timeout.to_s)
360
+ raise RuntimeError, "ERR timeout is not an integer or out of range"
361
+ elsif timeout < 0
362
+ raise RuntimeError, "ERR timeout is negative"
363
+ end
364
+ timeout
365
+ end
366
+
367
+ def can_incr?(value)
368
+ value.nil? || looks_like_integer?(value)
369
+ end
370
+
371
+ def extract_timeout(arglist)
372
+ timeout = assert_valid_timeout(arglist.last)
373
+ [arglist[0..-2], arglist.last]
374
+ end
375
+
376
+ def expiration(key)
377
+ expire_times.find {|(_,k)| k == key}.first
378
+ end
379
+
380
+ def has_expiration?(key)
381
+ expire_times.any? {|(_,k)| k == key}
382
+ end
383
+
384
+ def looks_like_integer?(str)
385
+ str =~ /^-?\d+$/
386
+ end
387
+
388
+ def redis_pattern_to_ruby_regex(pattern)
389
+ Regexp.new(
390
+ "^#{pattern}$".
391
+ gsub(/([^\\])\?/, "\\1.").
392
+ gsub(/([^\\])\*/, "\\1.+"))
393
+ end
394
+
395
+ def remove_expiration(key)
396
+ expire_times.delete_if do |(t, k)|
397
+ key == k
398
+ end
399
+ end
400
+
401
+ def set_expiration(key, time)
402
+ remove_expiration(key)
403
+
404
+ expire_times << [time, key]
405
+ expire_times.sort! do |a, b|
406
+ a.first <=> b.first
407
+ end
408
+ end
409
+
410
+ def zero_pad(string, desired_length)
411
+ padding = "\000" * [(desired_length - string.length), 0].max
412
+ string + padding
413
+ end
414
+
415
+ public
416
+ # This method isn't private, but it also isn't a Redis command, so
417
+ # it doesn't belong up above with all the Redis commands.
418
+ def expire_keys
419
+ now = Time.now
420
+
421
+ to_delete = expire_times.take_while do |(time, key)|
422
+ time <= now
423
+ end
424
+
425
+ to_delete.each do |(time, key)|
426
+ del(key)
427
+ end
428
+
429
+ expire_times.slice!(0, to_delete.length)
430
+ end
431
+ end
432
+ end
@@ -0,0 +1,6 @@
1
+ class MockRedis
2
+ def pipelined(options = {})
3
+ yield
4
+ nil
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ class MockRedis
2
+ WouldBlock = Class.new(StandardError)
3
+ end
@@ -0,0 +1,25 @@
1
+ require 'mock_redis/undef_redis_methods'
2
+
3
+ class MockRedis
4
+ class ExpireWrapper
5
+ include UndefRedisMethods
6
+
7
+ def respond_to?(method, include_private=false)
8
+ super || @db.respond_to?(method)
9
+ end
10
+
11
+ def initialize(db)
12
+ @db = db
13
+ end
14
+
15
+ def method_missing(method, *args)
16
+ @db.expire_keys
17
+ @db.send(method, *args)
18
+ end
19
+
20
+ def initialize_copy(source)
21
+ super
22
+ @db = @db.clone
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,118 @@
1
+ require 'mock_redis/assertions'
2
+ require 'mock_redis/utility_methods'
3
+
4
+ class MockRedis
5
+ module HashMethods
6
+ include Assertions
7
+ include UtilityMethods
8
+
9
+ def hdel(key, field)
10
+ with_hash_at(key) do |hash|
11
+ hash.delete(field.to_s) ? 1 : 0
12
+ end
13
+ end
14
+
15
+ def hexists(key, field)
16
+ with_hash_at(key) {|h| h.has_key?(field.to_s)}
17
+ end
18
+
19
+ def hget(key, field)
20
+ with_hash_at(key) {|h| h[field.to_s]}
21
+ end
22
+
23
+ def hgetall(key)
24
+ with_hash_at(key) {|h| h}
25
+ end
26
+
27
+ def hincrby(key, field, increment)
28
+ with_hash_at(key) do |hash|
29
+ field = field.to_s
30
+ unless can_incr?(data[key][field])
31
+ raise RuntimeError, "ERR hash value is not an integer"
32
+ end
33
+ unless looks_like_integer?(increment.to_s)
34
+ raise RuntimeError, "ERR value is not an integer or out of range"
35
+ end
36
+
37
+ new_value = (hash[field] || "0").to_i + increment.to_i
38
+ hash[field] = new_value.to_s
39
+ new_value
40
+ end
41
+ end
42
+
43
+ def hkeys(key)
44
+ with_hash_at(key, &:keys)
45
+ end
46
+
47
+ def hlen(key)
48
+ hkeys(key).length
49
+ end
50
+
51
+ def hmget(key, *fields)
52
+ assert_has_args(fields, 'hmget')
53
+ fields.map{|f| hget(key, f)}
54
+ end
55
+
56
+ def mapped_hmget(key, *fields)
57
+ reply = hmget(key, *fields)
58
+ Hash[*fields.zip(reply).flatten]
59
+ end
60
+
61
+ def hmset(key, *kvpairs)
62
+ assert_has_args(kvpairs, 'hmset')
63
+ if kvpairs.length.odd?
64
+ raise RuntimeError, "ERR wrong number of arguments for HMSET"
65
+ end
66
+
67
+ kvpairs.each_slice(2) do |(k,v)|
68
+ hset(key, k, v)
69
+ end
70
+ 'OK'
71
+ end
72
+
73
+ def mapped_hmset(key, hash)
74
+ kvpairs = hash.to_a.flatten
75
+ assert_has_args(kvpairs, 'hmset')
76
+ if kvpairs.length.odd?
77
+ raise RuntimeError, "ERR wrong number of arguments for 'hmset' command"
78
+ end
79
+
80
+ hmset(key, *kvpairs)
81
+ end
82
+
83
+ def hset(key, field, value)
84
+ with_hash_at(key) {|h| h[field.to_s] = value.to_s}
85
+ true
86
+ end
87
+
88
+ def hsetnx(key, field, value)
89
+ if hget(key, field)
90
+ false
91
+ else
92
+ hset(key, field, value)
93
+ true
94
+ end
95
+ end
96
+
97
+ def hvals(key)
98
+ with_hash_at(key, &:values)
99
+ end
100
+
101
+ private
102
+
103
+ def with_hash_at(key, &blk)
104
+ with_thing_at(key, :assert_hashy, proc {{}}, &blk)
105
+ end
106
+
107
+ def hashy?(key)
108
+ data[key].nil? || data[key].kind_of?(Hash)
109
+ end
110
+
111
+ def assert_hashy(key)
112
+ unless hashy?(key)
113
+ raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
114
+ end
115
+ end
116
+
117
+ end
118
+ end