lunar 0.1.0
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/examples/ohm.rb +23 -0
- data/lib/lunar.rb +12 -0
- data/lib/lunar/doc.rb +13 -0
- data/lib/lunar/index.rb +70 -0
- data/lib/lunar/scoring.rb +11 -0
- data/test/helper.rb +13 -0
- data/test/test_lunar.rb +4 -0
- data/test/test_lunar_document.rb +20 -0
- data/test/test_lunar_index.rb +174 -0
- data/test/test_lunar_scoring.rb +26 -0
- data/vendor/nest/nest.rb +7 -0
- data/vendor/redis/.gitignore +9 -0
- data/vendor/redis/LICENSE +20 -0
- data/vendor/redis/README.markdown +120 -0
- data/vendor/redis/Rakefile +75 -0
- data/vendor/redis/benchmarking/logging.rb +62 -0
- data/vendor/redis/benchmarking/pipeline.rb +44 -0
- data/vendor/redis/benchmarking/speed.rb +21 -0
- data/vendor/redis/benchmarking/suite.rb +24 -0
- data/vendor/redis/benchmarking/worker.rb +71 -0
- data/vendor/redis/bin/distredis +33 -0
- data/vendor/redis/examples/basic.rb +15 -0
- data/vendor/redis/examples/dist_redis.rb +43 -0
- data/vendor/redis/examples/incr-decr.rb +17 -0
- data/vendor/redis/examples/list.rb +26 -0
- data/vendor/redis/examples/pubsub.rb +25 -0
- data/vendor/redis/examples/sets.rb +36 -0
- data/vendor/redis/lib/edis.rb +3 -0
- data/vendor/redis/lib/redis.rb +496 -0
- data/vendor/redis/lib/redis/client.rb +265 -0
- data/vendor/redis/lib/redis/dist_redis.rb +118 -0
- data/vendor/redis/lib/redis/distributed.rb +460 -0
- data/vendor/redis/lib/redis/hash_ring.rb +131 -0
- data/vendor/redis/lib/redis/pipeline.rb +13 -0
- data/vendor/redis/lib/redis/raketasks.rb +1 -0
- data/vendor/redis/lib/redis/subscribe.rb +79 -0
- data/vendor/redis/profile.rb +22 -0
- data/vendor/redis/tasks/redis.tasks.rb +140 -0
- data/vendor/redis/test/db/.gitignore +1 -0
- data/vendor/redis/test/distributed_test.rb +1131 -0
- data/vendor/redis/test/redis_test.rb +1134 -0
- data/vendor/redis/test/test.conf +8 -0
- data/vendor/redis/test/test_helper.rb +113 -0
- metadata +127 -0
@@ -0,0 +1,265 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class Client
|
5
|
+
MINUS = "-".freeze
|
6
|
+
PLUS = "+".freeze
|
7
|
+
COLON = ":".freeze
|
8
|
+
DOLLAR = "$".freeze
|
9
|
+
ASTERISK = "*".freeze
|
10
|
+
|
11
|
+
attr_accessor :db, :host, :port, :password, :logger
|
12
|
+
attr :timeout
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
@host = options[:host] || "127.0.0.1"
|
16
|
+
@port = (options[:port] || 6379).to_i
|
17
|
+
@db = (options[:db] || 0).to_i
|
18
|
+
@timeout = (options[:timeout] || 5).to_i
|
19
|
+
@password = options[:password]
|
20
|
+
@logger = options[:logger]
|
21
|
+
@mutex = ::Mutex.new
|
22
|
+
@sock = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def connect
|
26
|
+
connect_to(@host, @port)
|
27
|
+
call(:auth, @password) if @password
|
28
|
+
call(:select, @db) if @db != 0
|
29
|
+
@sock
|
30
|
+
end
|
31
|
+
|
32
|
+
def id
|
33
|
+
"redis://#{host}:#{port}/#{db}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(*args)
|
37
|
+
process(args) do
|
38
|
+
read
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def call_loop(*args)
|
43
|
+
process(args) do
|
44
|
+
loop { yield(read) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def call_pipelined(commands)
|
49
|
+
process(*commands) do
|
50
|
+
Array.new(commands.size) { read }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def call_without_timeout(*args)
|
55
|
+
without_socket_timeout do
|
56
|
+
call(*args)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def process(*commands)
|
61
|
+
logging(commands) do
|
62
|
+
ensure_connected do
|
63
|
+
@sock.write(join_commands(commands))
|
64
|
+
yield if block_given?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def connected?
|
70
|
+
!! @sock
|
71
|
+
end
|
72
|
+
|
73
|
+
def disconnect
|
74
|
+
return unless connected?
|
75
|
+
|
76
|
+
begin
|
77
|
+
@sock.close
|
78
|
+
rescue
|
79
|
+
ensure
|
80
|
+
@sock = nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def reconnect
|
85
|
+
disconnect
|
86
|
+
connect
|
87
|
+
end
|
88
|
+
|
89
|
+
def read
|
90
|
+
|
91
|
+
# We read the first byte using read() mainly because gets() is
|
92
|
+
# immune to raw socket timeouts.
|
93
|
+
begin
|
94
|
+
reply_type = @sock.read(1)
|
95
|
+
rescue Errno::EAGAIN
|
96
|
+
|
97
|
+
# We want to make sure it reconnects on the next command after the
|
98
|
+
# timeout. Otherwise the server may reply in the meantime leaving
|
99
|
+
# the protocol in a desync status.
|
100
|
+
disconnect
|
101
|
+
|
102
|
+
raise Errno::EAGAIN, "Timeout reading from the socket"
|
103
|
+
end
|
104
|
+
|
105
|
+
raise Errno::ECONNRESET, "Connection lost" unless reply_type
|
106
|
+
|
107
|
+
format_reply(reply_type, @sock.gets)
|
108
|
+
end
|
109
|
+
|
110
|
+
def without_socket_timeout
|
111
|
+
ensure_connected do
|
112
|
+
begin
|
113
|
+
self.timeout = 0
|
114
|
+
yield
|
115
|
+
ensure
|
116
|
+
self.timeout = @timeout
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
protected
|
122
|
+
|
123
|
+
def build_command(name, *args)
|
124
|
+
command = []
|
125
|
+
command << "*#{args.size + 1}"
|
126
|
+
command << "$#{string_size name}"
|
127
|
+
command << name
|
128
|
+
|
129
|
+
args.each do |arg|
|
130
|
+
arg = arg.to_s
|
131
|
+
command << "$#{string_size arg}"
|
132
|
+
command << arg
|
133
|
+
end
|
134
|
+
|
135
|
+
command
|
136
|
+
end
|
137
|
+
|
138
|
+
def deprecated(old, new = nil, trace = caller[0])
|
139
|
+
message = "The method #{old} is deprecated and will be removed in 2.0"
|
140
|
+
message << " - use #{new} instead" if new
|
141
|
+
Redis.deprecate(message, trace)
|
142
|
+
end
|
143
|
+
|
144
|
+
COMMAND_DELIMITER = "\r\n"
|
145
|
+
|
146
|
+
def join_commands(commands)
|
147
|
+
commands.map do |command|
|
148
|
+
build_command(*command).join(COMMAND_DELIMITER) + COMMAND_DELIMITER
|
149
|
+
end.join(COMMAND_DELIMITER) + COMMAND_DELIMITER
|
150
|
+
end
|
151
|
+
|
152
|
+
if "".respond_to?(:bytesize)
|
153
|
+
def string_size(string)
|
154
|
+
string.to_s.bytesize
|
155
|
+
end
|
156
|
+
else
|
157
|
+
def string_size(string)
|
158
|
+
string.to_s.size
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def format_reply(reply_type, line)
|
163
|
+
case reply_type
|
164
|
+
when MINUS then format_error_reply(line)
|
165
|
+
when PLUS then format_status_reply(line)
|
166
|
+
when COLON then format_integer_reply(line)
|
167
|
+
when DOLLAR then format_bulk_reply(line)
|
168
|
+
when ASTERISK then format_multi_bulk_reply(line)
|
169
|
+
else raise ProtocolError.new(reply_type)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def format_error_reply(line)
|
174
|
+
raise "-" + line.strip
|
175
|
+
end
|
176
|
+
|
177
|
+
def format_status_reply(line)
|
178
|
+
line.strip
|
179
|
+
end
|
180
|
+
|
181
|
+
def format_integer_reply(line)
|
182
|
+
line.to_i
|
183
|
+
end
|
184
|
+
|
185
|
+
def format_bulk_reply(line)
|
186
|
+
bulklen = line.to_i
|
187
|
+
return if bulklen == -1
|
188
|
+
reply = @sock.read(bulklen)
|
189
|
+
@sock.read(2) # Discard CRLF.
|
190
|
+
reply
|
191
|
+
end
|
192
|
+
|
193
|
+
def format_multi_bulk_reply(line)
|
194
|
+
reply = []
|
195
|
+
line.to_i.times { reply << read }
|
196
|
+
reply
|
197
|
+
end
|
198
|
+
|
199
|
+
def logging(commands)
|
200
|
+
return yield unless @logger && @logger.debug?
|
201
|
+
|
202
|
+
begin
|
203
|
+
commands.each do |name, *args|
|
204
|
+
@logger.debug("Redis >> #{name.to_s.upcase} #{args.join(" ")}")
|
205
|
+
end
|
206
|
+
|
207
|
+
t1 = Time.now
|
208
|
+
yield
|
209
|
+
ensure
|
210
|
+
@logger.debug("Redis >> %0.2fms" % ((Time.now - t1) * 1000))
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
if defined?(Timeout)
|
215
|
+
TimeoutError = Timeout::Error
|
216
|
+
else
|
217
|
+
TimeoutError = Exception
|
218
|
+
end
|
219
|
+
|
220
|
+
def connect_to(host, port)
|
221
|
+
begin
|
222
|
+
@sock = TCPSocket.new(host, port)
|
223
|
+
rescue TimeoutError
|
224
|
+
@sock = nil
|
225
|
+
raise
|
226
|
+
end
|
227
|
+
|
228
|
+
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
229
|
+
|
230
|
+
# If the timeout is set we set the low level socket options in order
|
231
|
+
# to make sure a blocking read will return after the specified number
|
232
|
+
# of seconds. This hack is from memcached ruby client.
|
233
|
+
self.timeout = @timeout
|
234
|
+
|
235
|
+
rescue Errno::ECONNREFUSED
|
236
|
+
raise Errno::ECONNREFUSED, "Unable to connect to Redis on #{host}:#{port}"
|
237
|
+
end
|
238
|
+
|
239
|
+
def timeout=(timeout)
|
240
|
+
secs = Integer(timeout)
|
241
|
+
usecs = Integer((timeout - secs) * 1_000_000)
|
242
|
+
optval = [secs, usecs].pack("l_2")
|
243
|
+
|
244
|
+
begin
|
245
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
246
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
247
|
+
rescue Errno::ENOPROTOOPT
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def ensure_connected
|
252
|
+
connect unless connected?
|
253
|
+
|
254
|
+
begin
|
255
|
+
yield
|
256
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED
|
257
|
+
if reconnect
|
258
|
+
yield
|
259
|
+
else
|
260
|
+
raise Errno::ECONNRESET
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'redis/hash_ring'
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class DistRedis
|
5
|
+
attr_reader :ring
|
6
|
+
def initialize(opts={})
|
7
|
+
hosts = []
|
8
|
+
|
9
|
+
db = opts[:db] || nil
|
10
|
+
timeout = opts[:timeout] || nil
|
11
|
+
|
12
|
+
raise "No hosts given" unless opts[:hosts]
|
13
|
+
|
14
|
+
opts[:hosts].each do |h|
|
15
|
+
host, port = h.split(':')
|
16
|
+
hosts << Client.new(:host => host, :port => port, :db => db, :timeout => timeout)
|
17
|
+
end
|
18
|
+
|
19
|
+
@ring = HashRing.new hosts
|
20
|
+
end
|
21
|
+
|
22
|
+
def node_for_key(key)
|
23
|
+
key = $1 if key =~ /\{(.*)?\}/
|
24
|
+
@ring.get_node(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_server(server)
|
28
|
+
server, port = server.split(':')
|
29
|
+
@ring.add_node Client.new(:host => server, :port => port)
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing(sym, *args, &blk)
|
33
|
+
if redis = node_for_key(args.first.to_s)
|
34
|
+
redis.send sym, *args, &blk
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def node_keys(glob)
|
41
|
+
@ring.nodes.map do |red|
|
42
|
+
red.keys(glob)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def keys(glob)
|
47
|
+
node_keys(glob).flatten
|
48
|
+
end
|
49
|
+
|
50
|
+
def save
|
51
|
+
on_each_node :save
|
52
|
+
end
|
53
|
+
|
54
|
+
def bgsave
|
55
|
+
on_each_node :bgsave
|
56
|
+
end
|
57
|
+
|
58
|
+
def quit
|
59
|
+
on_each_node :quit
|
60
|
+
end
|
61
|
+
|
62
|
+
def flush_all
|
63
|
+
on_each_node :flush_all
|
64
|
+
end
|
65
|
+
alias_method :flushall, :flush_all
|
66
|
+
|
67
|
+
def flush_db
|
68
|
+
on_each_node :flush_db
|
69
|
+
end
|
70
|
+
alias_method :flushdb, :flush_db
|
71
|
+
|
72
|
+
def delete_cloud!
|
73
|
+
@ring.nodes.each do |red|
|
74
|
+
red.keys("*").each do |key|
|
75
|
+
red.delete key
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def on_each_node(command, *args)
|
81
|
+
@ring.nodes.each do |red|
|
82
|
+
red.send(command, *args)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def mset()
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def mget(*keyz)
|
91
|
+
results = {}
|
92
|
+
kbn = keys_by_node(keyz)
|
93
|
+
kbn.each do |node, node_keyz|
|
94
|
+
node.mapped_mget(*node_keyz).each do |k, v|
|
95
|
+
results[k] = v
|
96
|
+
end
|
97
|
+
end
|
98
|
+
keyz.flatten.map { |k| results[k] }
|
99
|
+
end
|
100
|
+
|
101
|
+
def keys_by_node(*keyz)
|
102
|
+
keyz.flatten.inject({}) do |kbn, k|
|
103
|
+
node = node_for_key(k)
|
104
|
+
next if kbn[node] && kbn[node].include?(k)
|
105
|
+
kbn[node] ||= []
|
106
|
+
kbn[node] << k
|
107
|
+
kbn
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def type(key)
|
112
|
+
method_missing(:type, key)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# For backwards compatibility
|
118
|
+
DistRedis = Redis::DistRedis
|
@@ -0,0 +1,460 @@
|
|
1
|
+
require "redis/hash_ring"
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class Distributed
|
5
|
+
|
6
|
+
class CannotDistribute < RuntimeError
|
7
|
+
def initialize(command)
|
8
|
+
@command = command
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :ring
|
17
|
+
|
18
|
+
def initialize(urls, options = {})
|
19
|
+
@tag = options.delete(:tag) || /^{(.+?)}/
|
20
|
+
@default_options = options
|
21
|
+
@ring = HashRing.new urls.map { |url| Redis.connect(options.merge(:url => url)) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def node_for(key)
|
25
|
+
@ring.get_node(key_tag(key) || key)
|
26
|
+
end
|
27
|
+
|
28
|
+
def nodes
|
29
|
+
@ring.nodes
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_node(url)
|
33
|
+
@ring.add_node Redis.connect(@default_options.merge(:url => url))
|
34
|
+
end
|
35
|
+
|
36
|
+
def quit
|
37
|
+
on_each_node :quit
|
38
|
+
end
|
39
|
+
|
40
|
+
def select(db)
|
41
|
+
on_each_node :select, db
|
42
|
+
end
|
43
|
+
|
44
|
+
def ping
|
45
|
+
on_each_node :ping
|
46
|
+
end
|
47
|
+
|
48
|
+
def flushall
|
49
|
+
on_each_node :flushall
|
50
|
+
end
|
51
|
+
|
52
|
+
def exists(key)
|
53
|
+
node_for(key).exists(key)
|
54
|
+
end
|
55
|
+
|
56
|
+
def del(*keys)
|
57
|
+
on_each_node(:del, *keys)
|
58
|
+
end
|
59
|
+
|
60
|
+
def type(key)
|
61
|
+
node_for(key).type(key)
|
62
|
+
end
|
63
|
+
|
64
|
+
def keys(glob = "*")
|
65
|
+
on_each_node(:keys, glob).flatten
|
66
|
+
end
|
67
|
+
|
68
|
+
def randomkey
|
69
|
+
raise CannotDistribute, :randomkey
|
70
|
+
end
|
71
|
+
|
72
|
+
def rename(old_name, new_name)
|
73
|
+
ensure_same_node(:rename, old_name, new_name) do |node|
|
74
|
+
node.rename(old_name, new_name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def renamenx(old_name, new_name)
|
79
|
+
ensure_same_node(:renamenx, old_name, new_name) do |node|
|
80
|
+
node.renamenx(old_name, new_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def dbsize
|
85
|
+
on_each_node :dbsize
|
86
|
+
end
|
87
|
+
|
88
|
+
def expire(key, seconds)
|
89
|
+
node_for(key).expire(key, seconds)
|
90
|
+
end
|
91
|
+
|
92
|
+
def expireat(key, unix_time)
|
93
|
+
node_for(key).expireat(key, unix_time)
|
94
|
+
end
|
95
|
+
|
96
|
+
def ttl(key)
|
97
|
+
node_for(key).ttl(key)
|
98
|
+
end
|
99
|
+
|
100
|
+
def move(key, db)
|
101
|
+
node_for(key).move(key, db)
|
102
|
+
end
|
103
|
+
|
104
|
+
def flushdb
|
105
|
+
on_each_node :flushdb
|
106
|
+
end
|
107
|
+
|
108
|
+
def set(key, value)
|
109
|
+
node_for(key).set(key, value)
|
110
|
+
end
|
111
|
+
|
112
|
+
def setex(key, ttl, value)
|
113
|
+
node_for(key).setex(key, ttl, value)
|
114
|
+
end
|
115
|
+
|
116
|
+
def get(key)
|
117
|
+
node_for(key).get(key)
|
118
|
+
end
|
119
|
+
|
120
|
+
def getset(key, value)
|
121
|
+
node_for(key).getset(key, value)
|
122
|
+
end
|
123
|
+
|
124
|
+
def [](key)
|
125
|
+
get(key)
|
126
|
+
end
|
127
|
+
|
128
|
+
def []=(key,value)
|
129
|
+
set(key, value)
|
130
|
+
end
|
131
|
+
|
132
|
+
def mget(*keys)
|
133
|
+
raise CannotDistribute, :mget
|
134
|
+
end
|
135
|
+
|
136
|
+
def mapped_mget(*keys)
|
137
|
+
raise CannotDistribute, :mapped_mget
|
138
|
+
end
|
139
|
+
|
140
|
+
def setnx(key, value)
|
141
|
+
node_for(key).setnx(key, value)
|
142
|
+
end
|
143
|
+
|
144
|
+
def mset(*args)
|
145
|
+
raise CannotDistribute, :mset
|
146
|
+
end
|
147
|
+
|
148
|
+
def mapped_mset(hash)
|
149
|
+
mset(*hash.to_a.flatten)
|
150
|
+
end
|
151
|
+
|
152
|
+
def msetnx(*args)
|
153
|
+
raise CannotDistribute, :msetnx
|
154
|
+
end
|
155
|
+
|
156
|
+
def mapped_msetnx(hash)
|
157
|
+
raise CannotDistribute, :mapped_msetnx
|
158
|
+
end
|
159
|
+
|
160
|
+
def incr(key)
|
161
|
+
node_for(key).incr(key)
|
162
|
+
end
|
163
|
+
|
164
|
+
def incrby(key, increment)
|
165
|
+
node_for(key).incrby(key, increment)
|
166
|
+
end
|
167
|
+
|
168
|
+
def decr(key)
|
169
|
+
node_for(key).decr(key)
|
170
|
+
end
|
171
|
+
|
172
|
+
def decrby(key, decrement)
|
173
|
+
node_for(key).decrby(key, decrement)
|
174
|
+
end
|
175
|
+
|
176
|
+
def rpush(key, value)
|
177
|
+
node_for(key).rpush(key, value)
|
178
|
+
end
|
179
|
+
|
180
|
+
def lpush(key, value)
|
181
|
+
node_for(key).lpush(key, value)
|
182
|
+
end
|
183
|
+
|
184
|
+
def llen(key)
|
185
|
+
node_for(key).llen(key)
|
186
|
+
end
|
187
|
+
|
188
|
+
def lrange(key, start, stop)
|
189
|
+
node_for(key).lrange(key, start, stop)
|
190
|
+
end
|
191
|
+
|
192
|
+
def ltrim(key, start, stop)
|
193
|
+
node_for(key).ltrim(key, start, stop)
|
194
|
+
end
|
195
|
+
|
196
|
+
def lindex(key, index)
|
197
|
+
node_for(key).lindex(key, index)
|
198
|
+
end
|
199
|
+
|
200
|
+
def lset(key, index, value)
|
201
|
+
node_for(key).lset(key, index, value)
|
202
|
+
end
|
203
|
+
|
204
|
+
def lrem(key, count, value)
|
205
|
+
node_for(key).lrem(key, count, value)
|
206
|
+
end
|
207
|
+
|
208
|
+
def lpop(key)
|
209
|
+
node_for(key).lpop(key)
|
210
|
+
end
|
211
|
+
|
212
|
+
def rpop(key)
|
213
|
+
node_for(key).rpop(key)
|
214
|
+
end
|
215
|
+
|
216
|
+
def rpoplpush(source, destination)
|
217
|
+
ensure_same_node(:rpoplpush, source, destination) do |node|
|
218
|
+
node.rpoplpush(source, destination)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def blpop(key, timeout)
|
223
|
+
node_for(key).blpop(key, timeout)
|
224
|
+
end
|
225
|
+
|
226
|
+
def brpop(key, timeout)
|
227
|
+
node_for(key).brpop(key, timeout)
|
228
|
+
end
|
229
|
+
|
230
|
+
def sadd(key, value)
|
231
|
+
node_for(key).sadd(key, value)
|
232
|
+
end
|
233
|
+
|
234
|
+
def srem(key, value)
|
235
|
+
node_for(key).srem(key, value)
|
236
|
+
end
|
237
|
+
|
238
|
+
def spop(key)
|
239
|
+
node_for(key).spop(key)
|
240
|
+
end
|
241
|
+
|
242
|
+
def smove(source, destination, member)
|
243
|
+
ensure_same_node(:smove, source, destination, member) do |node|
|
244
|
+
node.smove(source, destination, member)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def scard(key)
|
249
|
+
node_for(key).scard(key)
|
250
|
+
end
|
251
|
+
|
252
|
+
def sismember(key, member)
|
253
|
+
node_for(key).sismember(key, member)
|
254
|
+
end
|
255
|
+
|
256
|
+
def sinter(*keys)
|
257
|
+
ensure_same_node(:sinter, *keys) do |node|
|
258
|
+
node.sinter(*keys)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def sinterstore(destination, *keys)
|
263
|
+
ensure_same_node(:sinterstore, destination, *keys) do |node|
|
264
|
+
node.sinterstore(destination, *keys)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def sunion(*keys)
|
269
|
+
ensure_same_node(:sunion, *keys) do |node|
|
270
|
+
node.sunion(*keys)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def sunionstore(destination, *keys)
|
275
|
+
ensure_same_node(:sunionstore, destination, *keys) do |node|
|
276
|
+
node.sunionstore(destination, *keys)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def sdiff(*keys)
|
281
|
+
ensure_same_node(:sdiff, *keys) do |node|
|
282
|
+
node.sdiff(*keys)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def sdiffstore(destination, *keys)
|
287
|
+
ensure_same_node(:sdiffstore, destination, *keys) do |node|
|
288
|
+
node.sdiffstore(destination, *keys)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def smembers(key)
|
293
|
+
node_for(key).smembers(key)
|
294
|
+
end
|
295
|
+
|
296
|
+
def srandmember(key)
|
297
|
+
node_for(key).srandmember(key)
|
298
|
+
end
|
299
|
+
|
300
|
+
def zadd(key, score, member)
|
301
|
+
node_for(key).zadd(key, score, member)
|
302
|
+
end
|
303
|
+
|
304
|
+
def zrem(key, member)
|
305
|
+
node_for(key).zrem(key, member)
|
306
|
+
end
|
307
|
+
|
308
|
+
def zincrby(key, increment, member)
|
309
|
+
node_for(key).zincrby(key, increment, member)
|
310
|
+
end
|
311
|
+
|
312
|
+
def zrange(key, start, stop, with_scores = false)
|
313
|
+
node_for(key).zrange(key, start, stop, with_scores)
|
314
|
+
end
|
315
|
+
|
316
|
+
def zrevrange(key, start, stop, with_scores = false)
|
317
|
+
node_for(key).zrevrange(key, start, stop, with_scores)
|
318
|
+
end
|
319
|
+
|
320
|
+
def zrangebyscore(key, min, max)
|
321
|
+
node_for(key).zrangebyscore(key, min, max)
|
322
|
+
end
|
323
|
+
|
324
|
+
def zcard(key)
|
325
|
+
node_for(key).zcard(key)
|
326
|
+
end
|
327
|
+
|
328
|
+
def zscore(key, member)
|
329
|
+
node_for(key).zscore(key, member)
|
330
|
+
end
|
331
|
+
|
332
|
+
def hset(key, field, value)
|
333
|
+
node_for(key).hset(key, field, value)
|
334
|
+
end
|
335
|
+
|
336
|
+
def hget(key, field)
|
337
|
+
node_for(key).hget(key, field)
|
338
|
+
end
|
339
|
+
|
340
|
+
def hdel(key, field)
|
341
|
+
node_for(key).hdel(key, field)
|
342
|
+
end
|
343
|
+
|
344
|
+
def hexists(key, field)
|
345
|
+
node_for(key).hexists(key, field)
|
346
|
+
end
|
347
|
+
|
348
|
+
def hlen(key)
|
349
|
+
node_for(key).hlen(key)
|
350
|
+
end
|
351
|
+
|
352
|
+
def hkeys(key)
|
353
|
+
node_for(key).hkeys(key)
|
354
|
+
end
|
355
|
+
|
356
|
+
def hvals(key)
|
357
|
+
node_for(key).hvals(key)
|
358
|
+
end
|
359
|
+
|
360
|
+
def hgetall(key)
|
361
|
+
node_for(key).hgetall(key)
|
362
|
+
end
|
363
|
+
|
364
|
+
def hmset(key, *attrs)
|
365
|
+
node_for(key).hmset(key, *attrs)
|
366
|
+
end
|
367
|
+
|
368
|
+
def sort(key, options = {})
|
369
|
+
keys = [key, options[:by], options[:store], *Array(options[:get])].compact
|
370
|
+
|
371
|
+
ensure_same_node(:sort, *keys) do |node|
|
372
|
+
node.sort(key, options)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def multi(&block)
|
377
|
+
raise CannotDistribute, :multi
|
378
|
+
end
|
379
|
+
|
380
|
+
def exec
|
381
|
+
raise CannotDistribute, :exec
|
382
|
+
end
|
383
|
+
|
384
|
+
def discard
|
385
|
+
raise CannotDistribute, :discard
|
386
|
+
end
|
387
|
+
|
388
|
+
def publish(channel, message)
|
389
|
+
raise NotImplementedError
|
390
|
+
end
|
391
|
+
|
392
|
+
def unsubscribe(*channels)
|
393
|
+
raise NotImplementedError
|
394
|
+
end
|
395
|
+
|
396
|
+
def subscribe(*channels, &block)
|
397
|
+
raise NotImplementedError
|
398
|
+
end
|
399
|
+
|
400
|
+
def punsubscribe(*channels)
|
401
|
+
raise NotImplementedError
|
402
|
+
end
|
403
|
+
|
404
|
+
def psubscribe(*channels, &block)
|
405
|
+
raise NotImplementedError
|
406
|
+
end
|
407
|
+
|
408
|
+
def save
|
409
|
+
on_each_node :save
|
410
|
+
end
|
411
|
+
|
412
|
+
def bgsave
|
413
|
+
on_each_node :bgsave
|
414
|
+
end
|
415
|
+
|
416
|
+
def lastsave
|
417
|
+
on_each_node :lastsave
|
418
|
+
end
|
419
|
+
|
420
|
+
def info
|
421
|
+
on_each_node :info
|
422
|
+
end
|
423
|
+
|
424
|
+
def monitor
|
425
|
+
raise NotImplementedError
|
426
|
+
end
|
427
|
+
|
428
|
+
def echo(value)
|
429
|
+
on_each_node :echo, value
|
430
|
+
end
|
431
|
+
|
432
|
+
def pipelined
|
433
|
+
raise CannotDistribute, :pipelined
|
434
|
+
end
|
435
|
+
|
436
|
+
protected
|
437
|
+
|
438
|
+
def on_each_node(command, *args)
|
439
|
+
nodes.map do |node|
|
440
|
+
node.send(command, *args)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def node_index_for(key)
|
445
|
+
nodes.index(node_for(key))
|
446
|
+
end
|
447
|
+
|
448
|
+
def key_tag(key)
|
449
|
+
key[@tag, 1] if @tag
|
450
|
+
end
|
451
|
+
|
452
|
+
def ensure_same_node(command, *keys)
|
453
|
+
tags = keys.map { |key| key_tag(key) }
|
454
|
+
|
455
|
+
raise CannotDistribute, command if tags.compact.uniq.size != 1
|
456
|
+
|
457
|
+
yield(node_for(keys.first))
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|