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