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.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/README.md +35 -0
- data/lib/redis_migrator/redis_helper.rb +52 -0
- data/lib/redis_migrator/redis_populator.rb +63 -0
- data/lib/redis_migrator.rb +96 -0
- data/migrator_benchmark.rb +22 -0
- data/redis_migrator.gemspec +20 -0
- data/spec/migrator_spec.rb +63 -0
- data/spec/mock_redis/lib/mock_redis/assertions.rb +13 -0
- data/spec/mock_redis/lib/mock_redis/database.rb +432 -0
- data/spec/mock_redis/lib/mock_redis/distributed.rb +6 -0
- data/spec/mock_redis/lib/mock_redis/exceptions.rb +3 -0
- data/spec/mock_redis/lib/mock_redis/expire_wrapper.rb +25 -0
- data/spec/mock_redis/lib/mock_redis/hash_methods.rb +118 -0
- data/spec/mock_redis/lib/mock_redis/list_methods.rb +187 -0
- data/spec/mock_redis/lib/mock_redis/multi_db_wrapper.rb +86 -0
- data/spec/mock_redis/lib/mock_redis/set_methods.rb +126 -0
- data/spec/mock_redis/lib/mock_redis/string_methods.rb +203 -0
- data/spec/mock_redis/lib/mock_redis/transaction_wrapper.rb +80 -0
- data/spec/mock_redis/lib/mock_redis/undef_redis_methods.rb +11 -0
- data/spec/mock_redis/lib/mock_redis/utility_methods.rb +25 -0
- data/spec/mock_redis/lib/mock_redis/version.rb +3 -0
- data/spec/mock_redis/lib/mock_redis/zset.rb +110 -0
- data/spec/mock_redis/lib/mock_redis/zset_methods.rb +210 -0
- data/spec/mock_redis/lib/mock_redis.rb +119 -0
- data/spec/redis_helper_spec.rb +58 -0
- data/spec/spec_helper.rb +29 -0
- metadata +107 -0
@@ -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
|