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,187 @@
1
+ require 'mock_redis/assertions'
2
+ require 'mock_redis/utility_methods'
3
+
4
+ class MockRedis
5
+ module ListMethods
6
+ include Assertions
7
+ include UtilityMethods
8
+
9
+ def blpop(*args)
10
+ lists, timeout = extract_timeout(args)
11
+ nonempty_list = first_nonempty_list(lists)
12
+
13
+ if nonempty_list
14
+ [nonempty_list, lpop(nonempty_list)]
15
+ elsif timeout > 0
16
+ nil
17
+ else
18
+ raise MockRedis::WouldBlock, "Can't block forever"
19
+ end
20
+ end
21
+
22
+ def brpop(*args)
23
+ lists, timeout = extract_timeout(args)
24
+ nonempty_list = first_nonempty_list(lists)
25
+
26
+ if nonempty_list
27
+ [nonempty_list, rpop(nonempty_list)]
28
+ elsif timeout > 0
29
+ nil
30
+ else
31
+ raise MockRedis::WouldBlock, "Can't block forever"
32
+ end
33
+ end
34
+
35
+ def brpoplpush(source, destination, timeout)
36
+ assert_valid_timeout(timeout)
37
+
38
+ if llen(source) > 0
39
+ rpoplpush(source, destination)
40
+ elsif timeout > 0
41
+ nil
42
+ else
43
+ raise MockRedis::WouldBlock, "Can't block forever"
44
+ end
45
+ end
46
+
47
+ def lindex(key, index)
48
+ with_list_at(key) {|l| l[index]}
49
+ end
50
+
51
+ def linsert(key, position, pivot, value)
52
+ unless %w[before after].include?(position.to_s)
53
+ raise RuntimeError, "ERR syntax error"
54
+ end
55
+
56
+ assert_listy(key)
57
+ return 0 unless data[key]
58
+
59
+ pivot_position = (0..llen(key) - 1).find do |i|
60
+ data[key][i] == pivot.to_s
61
+ end
62
+
63
+ return -1 unless pivot_position
64
+
65
+ insertion_index = if position.to_s == 'before'
66
+ pivot_position
67
+ else
68
+ pivot_position + 1
69
+ end
70
+
71
+ data[key].insert(insertion_index, value.to_s)
72
+ llen(key)
73
+ end
74
+
75
+ def llen(key)
76
+ with_list_at(key, &:length)
77
+ end
78
+
79
+ def lpop(key)
80
+ with_list_at(key, &:shift)
81
+ end
82
+
83
+ def lpush(key, value)
84
+ with_list_at(key) {|l| l.unshift(value.to_s)}
85
+ llen(key)
86
+ end
87
+
88
+ def lpushx(key, value)
89
+ assert_listy(key)
90
+ return 0 unless list_at?(key)
91
+ lpush(key, value)
92
+ end
93
+
94
+ def lrange(key, start, stop)
95
+ with_list_at(key) {|l| l[start..stop]}
96
+ end
97
+
98
+ def lrem(key, count, value)
99
+ count = count.to_i
100
+ value = value.to_s
101
+
102
+ with_list_at(key) do |list|
103
+ indices_with_value = (0..(llen(key) - 1)).find_all do |i|
104
+ list[i] == value
105
+ end
106
+
107
+ indices_to_delete = if count == 0
108
+ indices_with_value.reverse
109
+ elsif count > 0
110
+ indices_with_value.take(count).reverse
111
+ else
112
+ indices_with_value.reverse.take(-count)
113
+ end
114
+
115
+ indices_to_delete.each {|i| list.delete_at(i)}.length
116
+ end
117
+ end
118
+
119
+ def lset(key, index, value)
120
+ assert_listy(key)
121
+
122
+ unless list_at?(key)
123
+ raise RuntimeError, "ERR no such key"
124
+ end
125
+
126
+ unless (0...llen(key)).include?(index)
127
+ raise RuntimeError, "ERR index out of range"
128
+ end
129
+
130
+ data[key][index] = value.to_s
131
+ 'OK'
132
+ end
133
+
134
+ def ltrim(key, start, stop)
135
+ with_list_at(key) do |list|
136
+ list.replace(list[start..stop] || []) if list
137
+ 'OK'
138
+ end
139
+ end
140
+
141
+ def rpop(key)
142
+ with_list_at(key) {|list| list.pop if list}
143
+ end
144
+
145
+ def rpoplpush(source, destination)
146
+ value = rpop(source)
147
+ lpush(destination, value)
148
+ value
149
+ end
150
+
151
+ def rpush(key, value)
152
+ with_list_at(key) {|l| l.push(value.to_s)}
153
+ llen(key)
154
+ end
155
+
156
+ def rpushx(key, value)
157
+ assert_listy(key)
158
+ return 0 unless list_at?(key)
159
+ rpush(key, value)
160
+ end
161
+
162
+ private
163
+ def list_at?(key)
164
+ data[key] && listy?(key)
165
+ end
166
+
167
+ def with_list_at(key, &blk)
168
+ with_thing_at(key, :assert_listy, proc {[]}, &blk)
169
+ end
170
+
171
+ def listy?(key)
172
+ data[key].nil? || data[key].kind_of?(Array)
173
+ end
174
+
175
+ def assert_listy(key)
176
+ unless listy?(key)
177
+ # Not the most helpful error, but it's what redis-rb barfs up
178
+ raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
179
+ end
180
+ end
181
+
182
+ def first_nonempty_list(keys)
183
+ keys.find{|k| llen(k) > 0}
184
+ end
185
+
186
+ end
187
+ end
@@ -0,0 +1,86 @@
1
+ require 'mock_redis/undef_redis_methods'
2
+
3
+ class MockRedis
4
+ class MultiDbWrapper
5
+ include UndefRedisMethods
6
+
7
+ def initialize(db)
8
+ @db_index = 0
9
+
10
+ @prototype_db = db.clone
11
+
12
+ @databases = Hash.new {|h,k| h[k] = @prototype_db.clone}
13
+ @databases[@db_index] = db
14
+ end
15
+
16
+ def respond_to?(method, include_private=false)
17
+ super || current_db.respond_to?(method, include_private)
18
+ end
19
+
20
+ def method_missing(method, *args)
21
+ current_db.send(method, *args)
22
+ end
23
+
24
+ def initialize_copy(source)
25
+ super
26
+ @databases = @databases.clone
27
+ @databases.keys.each do |k|
28
+ @databases[k] = @databases[k].clone
29
+ end
30
+ end
31
+
32
+ # Redis commands
33
+ def flushall
34
+ @databases.values.each(&:flushdb)
35
+ 'OK'
36
+ end
37
+
38
+ def move(key, db_index)
39
+ src = current_db
40
+ dest = db(db_index)
41
+
42
+ if !src.exists(key) || dest.exists(key)
43
+ false
44
+ else
45
+ case current_db.type(key)
46
+ when 'hash'
47
+ dest.hmset(key, *(src.hgetall(key).map{|k,v| [k,v]}.flatten))
48
+ when 'list'
49
+ while value = src.rpop(key)
50
+ dest.lpush(key, value)
51
+ end
52
+ when 'set'
53
+ while value = src.spop(key)
54
+ dest.sadd(key, value)
55
+ end
56
+ when 'string'
57
+ dest.set(key, src.get(key))
58
+ when 'zset'
59
+ src.zrange(key, 0, -1, :with_scores => true).each_slice(2) do |(m,s)|
60
+ dest.zadd(key, s, m)
61
+ end
62
+ else
63
+ raise ArgumentError,
64
+ "Can't move a key of type #{current_db.type(key).inspect}"
65
+ end
66
+
67
+ src.del(key)
68
+ true
69
+ end
70
+ end
71
+
72
+ def select(db_index)
73
+ @db_index = db_index.to_i
74
+ 'OK'
75
+ end
76
+
77
+ private
78
+ def current_db
79
+ @databases[@db_index]
80
+ end
81
+
82
+ def db(index)
83
+ @databases[index]
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,126 @@
1
+ require 'mock_redis/assertions'
2
+ require 'mock_redis/utility_methods'
3
+
4
+ class MockRedis
5
+ module SetMethods
6
+ include Assertions
7
+ include UtilityMethods
8
+
9
+ def sadd(key, member)
10
+ with_set_at(key) {|s| !!s.add?(member.to_s)}
11
+ end
12
+
13
+ def scard(key)
14
+ with_set_at(key) {|s| s.length}
15
+ end
16
+
17
+ def sdiff(*keys)
18
+ assert_has_args(keys, 'sdiff')
19
+ with_sets_at(*keys) {|*sets| sets.reduce(&:-)}.to_a
20
+ end
21
+
22
+ def sdiffstore(destination, *keys)
23
+ assert_has_args(keys, 'sdiffstore')
24
+ with_set_at(destination) do |set|
25
+ set.replace(sdiff(*keys))
26
+ end
27
+ scard(destination)
28
+ end
29
+
30
+ def sinter(*keys)
31
+ assert_has_args(keys, 'sinter')
32
+
33
+ with_sets_at(*keys) do |*sets|
34
+ sets.reduce(&:&).to_a
35
+ end
36
+ end
37
+
38
+ def sinterstore(destination, *keys)
39
+ assert_has_args(keys, 'sinterstore')
40
+ with_set_at(destination) do |set|
41
+ set.replace(sinter(*keys))
42
+ end
43
+ scard(destination)
44
+ end
45
+
46
+ def sismember(key, member)
47
+ with_set_at(key) {|s| s.include?(member.to_s)}
48
+ end
49
+
50
+ def smembers(key)
51
+ with_set_at(key, &:to_a)
52
+ end
53
+
54
+ def smove(src, dest, member)
55
+ member = member.to_s
56
+
57
+ with_sets_at(src, dest) do |src_set, dest_set|
58
+ if src_set.delete?(member)
59
+ dest_set.add(member)
60
+ true
61
+ else
62
+ false
63
+ end
64
+ end
65
+ end
66
+
67
+ def spop(key)
68
+ with_set_at(key) do |set|
69
+ member = set.first
70
+ set.delete(member)
71
+ member
72
+ end
73
+ end
74
+
75
+ def srandmember(key)
76
+ members = with_set_at(key, &:to_a)
77
+ members[rand(members.length)]
78
+ end
79
+
80
+ def srem(key, member)
81
+ with_set_at(key) {|s| !!s.delete?(member.to_s)}
82
+ end
83
+
84
+ def sunion(*keys)
85
+ assert_has_args(keys, 'sunion')
86
+ with_sets_at(*keys) {|*sets| sets.reduce(&:+).to_a}
87
+ end
88
+
89
+ def sunionstore(destination, *keys)
90
+ assert_has_args(keys, 'sunionstore')
91
+ with_set_at(destination) do |dest_set|
92
+ dest_set.replace(sunion(*keys))
93
+ end
94
+ scard(destination)
95
+ end
96
+
97
+ private
98
+ def with_set_at(key, &blk)
99
+ with_thing_at(key, :assert_sety, proc {Set.new}, &blk)
100
+ end
101
+
102
+ def with_sets_at(*keys, &blk)
103
+ if keys.length == 1
104
+ with_set_at(keys.first, &blk)
105
+ else
106
+ with_set_at(keys.first) do |set|
107
+ with_sets_at(*(keys[1..-1])) do |*sets|
108
+ blk.call(*([set] + sets))
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ def sety?(key)
115
+ data[key].nil? || data[key].kind_of?(Set)
116
+ end
117
+
118
+ def assert_sety(key)
119
+ unless sety?(key)
120
+ # Not the most helpful error, but it's what redis-rb barfs up
121
+ raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
122
+ end
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,203 @@
1
+ require 'mock_redis/assertions'
2
+
3
+ class MockRedis
4
+ module StringMethods
5
+ include Assertions
6
+
7
+ def append(key, value)
8
+ assert_stringy(key)
9
+ data[key] ||= ""
10
+ data[key] << value
11
+ data[key].length
12
+ end
13
+
14
+ def decr(key)
15
+ decrby(key, 1)
16
+ end
17
+
18
+ def decrby(key, n)
19
+ incrby(key, -n)
20
+ end
21
+
22
+ def get(key)
23
+ assert_stringy(key)
24
+ data[key]
25
+ end
26
+
27
+ def [](key)
28
+ get(key)
29
+ end
30
+
31
+ def getbit(key, offset)
32
+ assert_stringy(key)
33
+
34
+ offset_of_byte = offset / 8
35
+ offset_within_byte = offset % 8
36
+
37
+ # String#getbyte would be lovely, but it's not in 1.8.7.
38
+ byte = (data[key] || "").each_byte.drop(offset_of_byte).first
39
+
40
+ if byte
41
+ (byte & (2**7 >> offset_within_byte)) > 0 ? 1 : 0
42
+ else
43
+ 0
44
+ end
45
+ end
46
+
47
+ def getrange(key, start, stop)
48
+ assert_stringy(key)
49
+ (data[key] || "")[start..stop]
50
+ end
51
+
52
+ def getset(key, value)
53
+ retval = get(key)
54
+ set(key, value)
55
+ retval
56
+ end
57
+
58
+ def incr(key)
59
+ incrby(key, 1)
60
+ end
61
+
62
+ def incrby(key, n)
63
+ assert_stringy(key)
64
+ unless can_incr?(data[key])
65
+ raise RuntimeError, "ERR value is not an integer or out of range"
66
+ end
67
+
68
+ unless looks_like_integer?(n.to_s)
69
+ raise RuntimeError, "ERR value is not an integer or out of range"
70
+ end
71
+
72
+ new_value = data[key].to_i + n.to_i
73
+ data[key] = new_value.to_s
74
+ # for some reason, redis-rb doesn't return this as a string.
75
+ new_value
76
+ end
77
+
78
+ def mget(*keys)
79
+ assert_has_args(keys, 'mget')
80
+
81
+ keys.map do |key|
82
+ get(key) if stringy?(key)
83
+ end
84
+ end
85
+
86
+ def mset(*kvpairs)
87
+ assert_has_args(kvpairs, 'mset')
88
+ if kvpairs.length.odd?
89
+ raise RuntimeError, "ERR wrong number of arguments for MSET"
90
+ end
91
+
92
+ kvpairs.each_slice(2) do |(k,v)|
93
+ set(k,v)
94
+ end
95
+
96
+ "OK"
97
+ end
98
+
99
+ def msetnx(*kvpairs)
100
+ assert_has_args(kvpairs, 'msetnx')
101
+
102
+ if kvpairs.each_slice(2).any? {|(k,v)| exists(k)}
103
+ 0
104
+ else
105
+ mset(*kvpairs)
106
+ 1
107
+ end
108
+ end
109
+
110
+ def set(key, value)
111
+ data[key] = value.to_s
112
+ 'OK'
113
+ end
114
+
115
+ def []=(key, value)
116
+ set(key, value)
117
+ end
118
+
119
+ def setbit(key, offset, value)
120
+ assert_stringy(key, "ERR bit is not an integer or out of range")
121
+ retval = getbit(key, offset)
122
+
123
+ str = data[key] || ""
124
+
125
+ offset_of_byte = offset / 8
126
+ offset_within_byte = offset % 8
127
+
128
+ if offset_of_byte >= str.bytesize
129
+ str = zero_pad(str, offset_of_byte+1)
130
+ end
131
+
132
+ char_index = byte_index = offset_within_char = 0
133
+ str.each_char do |c|
134
+ if byte_index < offset_of_byte
135
+ char_index += 1
136
+ byte_index += c.bytesize
137
+ else
138
+ offset_within_char = byte_index - offset_of_byte
139
+ break
140
+ end
141
+ end
142
+
143
+ char = str[char_index]
144
+ char = char.chr if char.respond_to?(:chr) # ruby 1.8 vs 1.9
145
+ char_as_number = char.each_byte.reduce(0) do |a, byte|
146
+ (a << 8) + byte
147
+ end
148
+ char_as_number |=
149
+ (2**((char.bytesize * 8)-1) >>
150
+ (offset_within_char * 8 + offset_within_byte))
151
+ str[char_index] = char_as_number.chr
152
+
153
+ data[key] = str
154
+ retval
155
+ end
156
+
157
+ def setex(key, seconds, value)
158
+ set(key, value)
159
+ expire(key, seconds)
160
+ 'OK'
161
+ end
162
+
163
+ def setnx(key, value)
164
+ if exists(key)
165
+ false
166
+ else
167
+ set(key, value)
168
+ true
169
+ end
170
+ end
171
+
172
+ def setrange(key, offset, value)
173
+ assert_stringy(key)
174
+ value = value.to_s
175
+ old_value = (data[key] || "")
176
+
177
+ prefix = zero_pad(old_value[0...offset], offset)
178
+ data[key] = prefix + value + (old_value[(offset + value.length)..-1] || "")
179
+ data[key].length
180
+ end
181
+
182
+ def strlen(key)
183
+ assert_stringy(key)
184
+ (data[key] || "").bytesize
185
+ end
186
+
187
+
188
+
189
+
190
+ private
191
+ def stringy?(key)
192
+ data[key].nil? || data[key].kind_of?(String)
193
+ end
194
+
195
+ def assert_stringy(key,
196
+ message="ERR Operation against a key holding the wrong kind of value")
197
+ unless stringy?(key)
198
+ raise RuntimeError, message
199
+ end
200
+ end
201
+
202
+ end
203
+ end
@@ -0,0 +1,80 @@
1
+ require 'mock_redis/undef_redis_methods'
2
+
3
+ class MockRedis
4
+ class TransactionWrapper
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
+ @queued_commands = []
14
+ @in_multi = false
15
+ end
16
+
17
+ def method_missing(method, *args)
18
+ if @in_multi
19
+ @queued_commands << [method, *args]
20
+ 'QUEUED'
21
+ else
22
+ @db.expire_keys
23
+ @db.send(method, *args)
24
+ end
25
+ end
26
+
27
+ def initialize_copy(source)
28
+ super
29
+ @db = @db.clone
30
+ @queued_commands = @queued_commands.clone
31
+ end
32
+
33
+ def discard
34
+ unless @in_multi
35
+ raise RuntimeError, "ERR DISCARD without MULTI"
36
+ end
37
+ @in_multi = false
38
+ @queued_commands = []
39
+ 'OK'
40
+ end
41
+
42
+ def exec
43
+ unless @in_multi
44
+ raise RuntimeError, "ERR EXEC without MULTI"
45
+ end
46
+ @in_multi = false
47
+ responses = @queued_commands.map do |cmd|
48
+ begin
49
+ send(*cmd)
50
+ rescue => e
51
+ e
52
+ end
53
+ end
54
+ @queued_commands = []
55
+ responses
56
+ end
57
+
58
+ def multi
59
+ if @in_multi
60
+ raise RuntimeError, "ERR MULTI calls can not be nested"
61
+ end
62
+ @in_multi = true
63
+ if block_given?
64
+ yield(self)
65
+ self.exec
66
+ else
67
+ 'OK'
68
+ end
69
+ end
70
+
71
+ def unwatch
72
+ 'OK'
73
+ end
74
+
75
+ def watch(_)
76
+ 'OK'
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,11 @@
1
+ class MockRedis
2
+ module UndefRedisMethods
3
+ def self.included(klass)
4
+ if klass.instance_methods.map(&:to_s).include?('type')
5
+ klass.send(:undef_method, 'type')
6
+ end
7
+ klass.send(:undef_method, 'exec')
8
+ klass.send(:undef_method, 'select')
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ class MockRedis
2
+ module UtilityMethods
3
+ private
4
+
5
+ def with_thing_at(key, assertion, empty_thing_generator)
6
+ begin
7
+ send(assertion, key)
8
+ data[key] ||= empty_thing_generator.call
9
+ data_key_ref = data[key]
10
+ ret = yield data[key]
11
+ data[key] = data_key_ref if data[key].nil?
12
+ ret
13
+ ensure
14
+ clean_up_empties_at(key)
15
+ end
16
+ end
17
+
18
+ def clean_up_empties_at(key)
19
+ if data[key] && data[key].empty?
20
+ del(key)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ class MockRedis
2
+ VERSION = '0.4.1'
3
+ end