lunar 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +53 -0
  6. data/VERSION +1 -0
  7. data/examples/ohm.rb +23 -0
  8. data/lib/lunar.rb +12 -0
  9. data/lib/lunar/doc.rb +13 -0
  10. data/lib/lunar/index.rb +70 -0
  11. data/lib/lunar/scoring.rb +11 -0
  12. data/test/helper.rb +13 -0
  13. data/test/test_lunar.rb +4 -0
  14. data/test/test_lunar_document.rb +20 -0
  15. data/test/test_lunar_index.rb +174 -0
  16. data/test/test_lunar_scoring.rb +26 -0
  17. data/vendor/nest/nest.rb +7 -0
  18. data/vendor/redis/.gitignore +9 -0
  19. data/vendor/redis/LICENSE +20 -0
  20. data/vendor/redis/README.markdown +120 -0
  21. data/vendor/redis/Rakefile +75 -0
  22. data/vendor/redis/benchmarking/logging.rb +62 -0
  23. data/vendor/redis/benchmarking/pipeline.rb +44 -0
  24. data/vendor/redis/benchmarking/speed.rb +21 -0
  25. data/vendor/redis/benchmarking/suite.rb +24 -0
  26. data/vendor/redis/benchmarking/worker.rb +71 -0
  27. data/vendor/redis/bin/distredis +33 -0
  28. data/vendor/redis/examples/basic.rb +15 -0
  29. data/vendor/redis/examples/dist_redis.rb +43 -0
  30. data/vendor/redis/examples/incr-decr.rb +17 -0
  31. data/vendor/redis/examples/list.rb +26 -0
  32. data/vendor/redis/examples/pubsub.rb +25 -0
  33. data/vendor/redis/examples/sets.rb +36 -0
  34. data/vendor/redis/lib/edis.rb +3 -0
  35. data/vendor/redis/lib/redis.rb +496 -0
  36. data/vendor/redis/lib/redis/client.rb +265 -0
  37. data/vendor/redis/lib/redis/dist_redis.rb +118 -0
  38. data/vendor/redis/lib/redis/distributed.rb +460 -0
  39. data/vendor/redis/lib/redis/hash_ring.rb +131 -0
  40. data/vendor/redis/lib/redis/pipeline.rb +13 -0
  41. data/vendor/redis/lib/redis/raketasks.rb +1 -0
  42. data/vendor/redis/lib/redis/subscribe.rb +79 -0
  43. data/vendor/redis/profile.rb +22 -0
  44. data/vendor/redis/tasks/redis.tasks.rb +140 -0
  45. data/vendor/redis/test/db/.gitignore +1 -0
  46. data/vendor/redis/test/distributed_test.rb +1131 -0
  47. data/vendor/redis/test/redis_test.rb +1134 -0
  48. data/vendor/redis/test/test.conf +8 -0
  49. data/vendor/redis/test/test_helper.rb +113 -0
  50. 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