ryansch-mock_redis 0.2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +9 -0
- data/LICENSE +19 -0
- data/README.md +94 -0
- data/Rakefile +10 -0
- data/lib/mock_redis.rb +32 -0
- data/lib/mock_redis/assertions.rb +13 -0
- data/lib/mock_redis/database.rb +432 -0
- data/lib/mock_redis/exceptions.rb +3 -0
- data/lib/mock_redis/expire_wrapper.rb +25 -0
- data/lib/mock_redis/hash_methods.rb +102 -0
- data/lib/mock_redis/list_methods.rb +187 -0
- data/lib/mock_redis/multi_db_wrapper.rb +86 -0
- data/lib/mock_redis/set_methods.rb +125 -0
- data/lib/mock_redis/string_methods.rb +195 -0
- data/lib/mock_redis/transaction_wrapper.rb +80 -0
- data/lib/mock_redis/undef_redis_methods.rb +11 -0
- data/lib/mock_redis/utility_methods.rb +22 -0
- data/lib/mock_redis/version.rb +3 -0
- data/lib/mock_redis/zset.rb +110 -0
- data/lib/mock_redis/zset_methods.rb +209 -0
- data/mock_redis.gemspec +24 -0
- data/spec/cloning_spec.rb +96 -0
- data/spec/commands/append_spec.rb +24 -0
- data/spec/commands/auth_spec.rb +7 -0
- data/spec/commands/bgrewriteaof_spec.rb +7 -0
- data/spec/commands/bgsave_spec.rb +7 -0
- data/spec/commands/blpop_spec.rb +55 -0
- data/spec/commands/brpop_spec.rb +54 -0
- data/spec/commands/brpoplpush_spec.rb +53 -0
- data/spec/commands/dbsize_spec.rb +18 -0
- data/spec/commands/decr_spec.rb +34 -0
- data/spec/commands/decrby_spec.rb +34 -0
- data/spec/commands/del_spec.rb +20 -0
- data/spec/commands/echo_spec.rb +11 -0
- data/spec/commands/exists_spec.rb +14 -0
- data/spec/commands/expire_spec.rb +83 -0
- data/spec/commands/expireat_spec.rb +48 -0
- data/spec/commands/flushall_spec.rb +38 -0
- data/spec/commands/flushdb_spec.rb +38 -0
- data/spec/commands/get_spec.rb +23 -0
- data/spec/commands/getbit_spec.rb +34 -0
- data/spec/commands/getrange_spec.rb +22 -0
- data/spec/commands/getset_spec.rb +23 -0
- data/spec/commands/hdel_spec.rb +35 -0
- data/spec/commands/hexists_spec.rb +22 -0
- data/spec/commands/hget_spec.rb +23 -0
- data/spec/commands/hgetall_spec.rb +22 -0
- data/spec/commands/hincrby_spec.rb +52 -0
- data/spec/commands/hkeys_spec.rb +19 -0
- data/spec/commands/hlen_spec.rb +19 -0
- data/spec/commands/hmget_spec.rb +30 -0
- data/spec/commands/hmset_spec.rb +43 -0
- data/spec/commands/hset_spec.rb +23 -0
- data/spec/commands/hsetnx_spec.rb +39 -0
- data/spec/commands/hvals_spec.rb +19 -0
- data/spec/commands/incr_spec.rb +34 -0
- data/spec/commands/incrby_spec.rb +44 -0
- data/spec/commands/info_spec.rb +13 -0
- data/spec/commands/keys_spec.rb +87 -0
- data/spec/commands/lastsave_spec.rb +8 -0
- data/spec/commands/lindex_spec.rb +39 -0
- data/spec/commands/linsert_spec.rb +68 -0
- data/spec/commands/llen_spec.rb +16 -0
- data/spec/commands/lpop_spec.rb +34 -0
- data/spec/commands/lpush_spec.rb +30 -0
- data/spec/commands/lpushx_spec.rb +33 -0
- data/spec/commands/lrange_spec.rb +35 -0
- data/spec/commands/lrem_spec.rb +79 -0
- data/spec/commands/lset_spec.rb +38 -0
- data/spec/commands/ltrim_spec.rb +35 -0
- data/spec/commands/mget_spec.rb +34 -0
- data/spec/commands/move_spec.rb +147 -0
- data/spec/commands/mset_spec.rb +29 -0
- data/spec/commands/msetnx_spec.rb +40 -0
- data/spec/commands/persist_spec.rb +49 -0
- data/spec/commands/ping_spec.rb +7 -0
- data/spec/commands/quit_spec.rb +7 -0
- data/spec/commands/randomkey_spec.rb +20 -0
- data/spec/commands/rename_spec.rb +31 -0
- data/spec/commands/renamenx_spec.rb +36 -0
- data/spec/commands/rpop_spec.rb +34 -0
- data/spec/commands/rpoplpush_spec.rb +45 -0
- data/spec/commands/rpush_spec.rb +30 -0
- data/spec/commands/rpushx_spec.rb +33 -0
- data/spec/commands/sadd_spec.rb +22 -0
- data/spec/commands/save_spec.rb +7 -0
- data/spec/commands/scard_spec.rb +18 -0
- data/spec/commands/sdiff_spec.rb +47 -0
- data/spec/commands/sdiffstore_spec.rb +58 -0
- data/spec/commands/select_spec.rb +53 -0
- data/spec/commands/set_spec.rb +7 -0
- data/spec/commands/setbit_spec.rb +46 -0
- data/spec/commands/setex_spec.rb +22 -0
- data/spec/commands/setnx_spec.rb +25 -0
- data/spec/commands/setrange_spec.rb +30 -0
- data/spec/commands/sinter_spec.rb +41 -0
- data/spec/commands/sinterstore_spec.rb +53 -0
- data/spec/commands/sismember_spec.rb +29 -0
- data/spec/commands/smembers_spec.rb +18 -0
- data/spec/commands/smove_spec.rb +41 -0
- data/spec/commands/spop_spec.rb +25 -0
- data/spec/commands/srandmember_spec.rb +25 -0
- data/spec/commands/srem_spec.rb +35 -0
- data/spec/commands/strlen_spec.rb +19 -0
- data/spec/commands/sunion_spec.rb +40 -0
- data/spec/commands/sunionstore_spec.rb +53 -0
- data/spec/commands/ttl_spec.rb +36 -0
- data/spec/commands/type_spec.rb +36 -0
- data/spec/commands/unwatch_spec.rb +7 -0
- data/spec/commands/watch_spec.rb +7 -0
- data/spec/commands/zadd_spec.rb +29 -0
- data/spec/commands/zcard_spec.rb +19 -0
- data/spec/commands/zcount_spec.rb +23 -0
- data/spec/commands/zincrby_spec.rb +24 -0
- data/spec/commands/zinterstore_spec.rb +96 -0
- data/spec/commands/zrange_spec.rb +31 -0
- data/spec/commands/zrangebyscore_spec.rb +68 -0
- data/spec/commands/zrank_spec.rb +23 -0
- data/spec/commands/zrem_spec.rb +25 -0
- data/spec/commands/zremrangebyrank_spec.rb +22 -0
- data/spec/commands/zremrangebyscore_spec.rb +28 -0
- data/spec/commands/zrevrange_spec.rb +31 -0
- data/spec/commands/zrevrangebyscore_spec.rb +47 -0
- data/spec/commands/zrevrank_spec.rb +23 -0
- data/spec/commands/zscore_spec.rb +16 -0
- data/spec/commands/zunionstore_spec.rb +104 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/redis_multiplexer.rb +91 -0
- data/spec/support/shared_examples/only_operates_on_hashes.rb +13 -0
- data/spec/support/shared_examples/only_operates_on_lists.rb +13 -0
- data/spec/support/shared_examples/only_operates_on_sets.rb +13 -0
- data/spec/support/shared_examples/only_operates_on_strings.rb +13 -0
- data/spec/support/shared_examples/only_operates_on_zsets.rb +57 -0
- data/spec/transactions_spec.rb +96 -0
- metadata +361 -0
@@ -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,102 @@
|
|
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) ? 1 : 0
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def hexists(key, field)
|
16
|
+
with_hash_at(key) {|h| h.has_key?(field)}
|
17
|
+
end
|
18
|
+
|
19
|
+
def hget(key, field)
|
20
|
+
with_hash_at(key) {|h| h[field]}
|
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
|
+
unless can_incr?(data[key][field])
|
30
|
+
raise RuntimeError, "ERR hash value is not an integer"
|
31
|
+
end
|
32
|
+
unless looks_like_integer?(increment.to_s)
|
33
|
+
raise RuntimeError, "ERR value is not an integer or out of range"
|
34
|
+
end
|
35
|
+
|
36
|
+
new_value = (hash[field] || "0").to_i + increment.to_i
|
37
|
+
hash[field] = new_value.to_s
|
38
|
+
new_value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def hkeys(key)
|
43
|
+
with_hash_at(key, &:keys)
|
44
|
+
end
|
45
|
+
|
46
|
+
def hlen(key)
|
47
|
+
hkeys(key).length
|
48
|
+
end
|
49
|
+
|
50
|
+
def hmget(key, *fields)
|
51
|
+
assert_has_args(fields, 'hmget')
|
52
|
+
fields.map{|f| hget(key, f)}
|
53
|
+
end
|
54
|
+
|
55
|
+
def hmset(key, *kvpairs)
|
56
|
+
assert_has_args(kvpairs, 'hmset')
|
57
|
+
if kvpairs.length.odd?
|
58
|
+
raise RuntimeError, "ERR wrong number of arguments for HMSET"
|
59
|
+
end
|
60
|
+
|
61
|
+
kvpairs.each_slice(2) do |(k,v)|
|
62
|
+
hset(key, k, v)
|
63
|
+
end
|
64
|
+
'OK'
|
65
|
+
end
|
66
|
+
|
67
|
+
def hset(key, field, value)
|
68
|
+
with_hash_at(key) {|h| h[field] = value.to_s}
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
def hsetnx(key, field, value)
|
73
|
+
if hget(key, field)
|
74
|
+
false
|
75
|
+
else
|
76
|
+
hset(key, field, value)
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def hvals(key)
|
82
|
+
with_hash_at(key, &:values)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def with_hash_at(key, &blk)
|
88
|
+
with_thing_at(key, :assert_hashy, proc {{}}, &blk)
|
89
|
+
end
|
90
|
+
|
91
|
+
def hashy?(key)
|
92
|
+
data[key].nil? || data[key].kind_of?(Hash)
|
93
|
+
end
|
94
|
+
|
95
|
+
def assert_hashy(key)
|
96
|
+
unless hashy?(key)
|
97
|
+
raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -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,125 @@
|
|
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
|
+
with_set_at(key, &:first)
|
77
|
+
end
|
78
|
+
|
79
|
+
def srem(key, member)
|
80
|
+
with_set_at(key) {|s| !!s.delete?(member.to_s)}
|
81
|
+
end
|
82
|
+
|
83
|
+
def sunion(*keys)
|
84
|
+
assert_has_args(keys, 'sunion')
|
85
|
+
with_sets_at(*keys) {|*sets| sets.reduce(&:+).to_a}
|
86
|
+
end
|
87
|
+
|
88
|
+
def sunionstore(destination, *keys)
|
89
|
+
assert_has_args(keys, 'sunionstore')
|
90
|
+
with_set_at(destination) do |dest_set|
|
91
|
+
dest_set.replace(sunion(*keys))
|
92
|
+
end
|
93
|
+
scard(destination)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def with_set_at(key, &blk)
|
98
|
+
with_thing_at(key, :assert_sety, proc {Set.new}, &blk)
|
99
|
+
end
|
100
|
+
|
101
|
+
def with_sets_at(*keys, &blk)
|
102
|
+
if keys.length == 1
|
103
|
+
with_set_at(keys.first, &blk)
|
104
|
+
else
|
105
|
+
with_set_at(keys.first) do |set|
|
106
|
+
with_sets_at(*(keys[1..-1])) do |*sets|
|
107
|
+
blk.call(*([set] + sets))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def sety?(key)
|
114
|
+
data[key].nil? || data[key].kind_of?(Set)
|
115
|
+
end
|
116
|
+
|
117
|
+
def assert_sety(key)
|
118
|
+
unless sety?(key)
|
119
|
+
# Not the most helpful error, but it's what redis-rb barfs up
|
120
|
+
raise RuntimeError, "ERR Operation against a key holding the wrong kind of value"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|