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,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