redis_migrator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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