yam-redis-with-retries 2.2.2.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.
Files changed (81) hide show
  1. data/CHANGELOG.md +53 -0
  2. data/LICENSE +20 -0
  3. data/README.md +208 -0
  4. data/Rakefile +277 -0
  5. data/benchmarking/logging.rb +62 -0
  6. data/benchmarking/pipeline.rb +51 -0
  7. data/benchmarking/speed.rb +21 -0
  8. data/benchmarking/suite.rb +24 -0
  9. data/benchmarking/thread_safety.rb +38 -0
  10. data/benchmarking/worker.rb +71 -0
  11. data/examples/basic.rb +15 -0
  12. data/examples/dist_redis.rb +43 -0
  13. data/examples/incr-decr.rb +17 -0
  14. data/examples/list.rb +26 -0
  15. data/examples/pubsub.rb +31 -0
  16. data/examples/sets.rb +36 -0
  17. data/examples/unicorn/config.ru +3 -0
  18. data/examples/unicorn/unicorn.rb +20 -0
  19. data/lib/redis.rb +1166 -0
  20. data/lib/redis/client.rb +265 -0
  21. data/lib/redis/compat.rb +21 -0
  22. data/lib/redis/connection.rb +9 -0
  23. data/lib/redis/connection/command_helper.rb +45 -0
  24. data/lib/redis/connection/hiredis.rb +49 -0
  25. data/lib/redis/connection/registry.rb +12 -0
  26. data/lib/redis/connection/ruby.rb +135 -0
  27. data/lib/redis/connection/synchrony.rb +129 -0
  28. data/lib/redis/distributed.rb +694 -0
  29. data/lib/redis/hash_ring.rb +131 -0
  30. data/lib/redis/pipeline.rb +34 -0
  31. data/lib/redis/retry.rb +128 -0
  32. data/lib/redis/subscribe.rb +94 -0
  33. data/lib/redis/version.rb +3 -0
  34. data/test/commands_on_hashes_test.rb +20 -0
  35. data/test/commands_on_lists_test.rb +60 -0
  36. data/test/commands_on_sets_test.rb +78 -0
  37. data/test/commands_on_sorted_sets_test.rb +109 -0
  38. data/test/commands_on_strings_test.rb +80 -0
  39. data/test/commands_on_value_types_test.rb +88 -0
  40. data/test/connection_handling_test.rb +88 -0
  41. data/test/distributed_blocking_commands_test.rb +53 -0
  42. data/test/distributed_commands_on_hashes_test.rb +12 -0
  43. data/test/distributed_commands_on_lists_test.rb +24 -0
  44. data/test/distributed_commands_on_sets_test.rb +85 -0
  45. data/test/distributed_commands_on_strings_test.rb +50 -0
  46. data/test/distributed_commands_on_value_types_test.rb +73 -0
  47. data/test/distributed_commands_requiring_clustering_test.rb +148 -0
  48. data/test/distributed_connection_handling_test.rb +25 -0
  49. data/test/distributed_internals_test.rb +27 -0
  50. data/test/distributed_key_tags_test.rb +53 -0
  51. data/test/distributed_persistence_control_commands_test.rb +24 -0
  52. data/test/distributed_publish_subscribe_test.rb +101 -0
  53. data/test/distributed_remote_server_control_commands_test.rb +43 -0
  54. data/test/distributed_sorting_test.rb +21 -0
  55. data/test/distributed_test.rb +59 -0
  56. data/test/distributed_transactions_test.rb +34 -0
  57. data/test/encoding_test.rb +16 -0
  58. data/test/error_replies_test.rb +53 -0
  59. data/test/helper.rb +145 -0
  60. data/test/internals_test.rb +163 -0
  61. data/test/lint/hashes.rb +126 -0
  62. data/test/lint/internals.rb +37 -0
  63. data/test/lint/lists.rb +93 -0
  64. data/test/lint/sets.rb +66 -0
  65. data/test/lint/sorted_sets.rb +167 -0
  66. data/test/lint/strings.rb +137 -0
  67. data/test/lint/value_types.rb +84 -0
  68. data/test/persistence_control_commands_test.rb +22 -0
  69. data/test/pipelining_commands_test.rb +123 -0
  70. data/test/publish_subscribe_test.rb +158 -0
  71. data/test/redis_mock.rb +80 -0
  72. data/test/remote_server_control_commands_test.rb +82 -0
  73. data/test/retry_test.rb +225 -0
  74. data/test/sorting_test.rb +44 -0
  75. data/test/synchrony_driver.rb +57 -0
  76. data/test/test.conf +8 -0
  77. data/test/thread_safety_test.rb +30 -0
  78. data/test/transactions_test.rb +100 -0
  79. data/test/unknown_commands_test.rb +14 -0
  80. data/test/url_param_test.rb +60 -0
  81. metadata +215 -0
@@ -0,0 +1,3 @@
1
+ run lambda { |env|
2
+ [200, {"Content-Type" => "text/plain"}, [$redis.randomkey]]
3
+ }
@@ -0,0 +1,20 @@
1
+ require "redis"
2
+
3
+ worker_processes 3
4
+
5
+ # If you set the connection to Redis *before* forking,
6
+ # you will cause forks to share a file descriptor.
7
+ #
8
+ # This causes a concurrency problem by which one fork
9
+ # can read or write to the socket while others are
10
+ # performing other operations.
11
+ #
12
+ # Most likely you'll be getting ProtocolError exceptions
13
+ # mentioning a wrong initial byte in the reply.
14
+ #
15
+ # Thus we need to connect to Redis after forking the
16
+ # worker processes.
17
+
18
+ after_fork do |server, worker|
19
+ $redis = Redis.connect
20
+ end
data/lib/redis.rb ADDED
@@ -0,0 +1,1166 @@
1
+ require "monitor"
2
+ require "redis/retry"
3
+
4
+ class Redis
5
+ class ProtocolError < RuntimeError
6
+ def initialize(reply_type)
7
+ super(<<-EOS.gsub(/(?:^|\n)\s*/, " "))
8
+ Got '#{reply_type}' as initial reply byte.
9
+ If you're running in a multi-threaded environment, make sure you
10
+ pass the :thread_safe option when initializing the connection.
11
+ If you're in a forking environment, such as Unicorn, you need to
12
+ connect to Redis after forking.
13
+ EOS
14
+ end
15
+ end
16
+
17
+ def self.deprecate(message, trace = caller[0])
18
+ $stderr.puts "\n#{message} (in #{trace})"
19
+ end
20
+
21
+ attr :client
22
+
23
+ def self.connect(options = {})
24
+ options = options.dup
25
+
26
+ require "uri"
27
+
28
+ url = URI(options.delete(:url) || ENV["REDIS_URL"] || "redis://127.0.0.1:6379/0")
29
+
30
+ options[:host] ||= url.host
31
+ options[:port] ||= url.port
32
+ options[:password] ||= url.password
33
+ options[:db] ||= url.path[1..-1].to_i
34
+
35
+ new(options)
36
+ end
37
+
38
+ def self.current
39
+ Thread.current[:redis] ||= Redis.connect
40
+ end
41
+
42
+ def self.current=(redis)
43
+ Thread.current[:redis] = redis
44
+ end
45
+
46
+ include MonitorMixin
47
+
48
+ def initialize(options = {})
49
+ super()
50
+ @options = options
51
+ @client = Client.new(options)
52
+ end
53
+
54
+ def wrapped
55
+ with_retry do
56
+ with_thread_safety do
57
+ yield
58
+ end
59
+ end
60
+ end
61
+
62
+ def with_retry(&block)
63
+ @with_retry ||= (@options.keys & Redis::Retry::DEFAULT_OPTS.keys).any?
64
+
65
+ if ! @with_retry
66
+ block.call
67
+ else
68
+ @retry ||= Redis::Retry.new(client, @options)
69
+ @retry.execute(&block)
70
+ end
71
+ end
72
+
73
+ def with_thread_safety(&block)
74
+ if @options[:thread_safe] != false
75
+ synchronize(&block)
76
+ else
77
+ block.call
78
+ end
79
+ end
80
+
81
+ # Run code without the client reconnecting
82
+ def without_reconnect(&block)
83
+ wrapped do
84
+ @client.without_reconnect(&block)
85
+ end
86
+ end
87
+
88
+ # Authenticate to the server.
89
+ def auth(password)
90
+ wrapped do
91
+ @client.call [:auth, password]
92
+ end
93
+ end
94
+
95
+ # Change the selected database for the current connection.
96
+ def select(db)
97
+ wrapped do
98
+ @client.db = db
99
+ @client.call [:select, db]
100
+ end
101
+ end
102
+
103
+ # Get information and statistics about the server.
104
+ def info(cmd = nil)
105
+ wrapped do
106
+ reply = @client.call [:info, cmd].compact
107
+
108
+ if reply.kind_of?(String)
109
+ reply = Hash[*reply.split(/:|\r\n/).grep(/^[^#]/)]
110
+
111
+ if cmd && cmd.to_s == "commandstats"
112
+ # Extract nested hashes for INFO COMMANDSTATS
113
+ reply = Hash[reply.map do |k, v|
114
+ [k[/^cmdstat_(.*)$/, 1], Hash[*v.split(/,|=/)]]
115
+ end]
116
+ end
117
+ end
118
+
119
+ reply
120
+ end
121
+ end
122
+
123
+ def config(action, *args)
124
+ wrapped do
125
+ reply = @client.call [:config, action, *args]
126
+
127
+ if reply.kind_of?(Array) && action == :get
128
+ Hash[*reply]
129
+ else
130
+ reply
131
+ end
132
+ end
133
+ end
134
+
135
+ # Remove all keys from the current database.
136
+ def flushdb
137
+ wrapped do
138
+ @client.call [:flushdb]
139
+ end
140
+ end
141
+
142
+ # Remove all keys from all databases.
143
+ def flushall
144
+ wrapped do
145
+ @client.call [:flushall]
146
+ end
147
+ end
148
+
149
+ # Synchronously save the dataset to disk.
150
+ def save
151
+ wrapped do
152
+ @client.call [:save]
153
+ end
154
+ end
155
+
156
+ # Asynchronously save the dataset to disk.
157
+ def bgsave
158
+ wrapped do
159
+ @client.call [:bgsave]
160
+ end
161
+ end
162
+
163
+ # Asynchronously rewrite the append-only file.
164
+ def bgrewriteaof
165
+ wrapped do
166
+ @client.call [:bgrewriteaof]
167
+ end
168
+ end
169
+
170
+ # Get the value of a key.
171
+ def get(key)
172
+ wrapped do
173
+ @client.call [:get, key]
174
+ end
175
+ end
176
+
177
+ # Returns the bit value at offset in the string value stored at key.
178
+ def getbit(key, offset)
179
+ wrapped do
180
+ @client.call [:getbit, key, offset]
181
+ end
182
+ end
183
+
184
+ # Get a substring of the string stored at a key.
185
+ def getrange(key, start, stop)
186
+ wrapped do
187
+ @client.call [:getrange, key, start, stop]
188
+ end
189
+ end
190
+
191
+ # Set the string value of a key and return its old value.
192
+ def getset(key, value)
193
+ wrapped do
194
+ @client.call [:getset, key, value]
195
+ end
196
+ end
197
+
198
+ # Get the values of all the given keys.
199
+ def mget(*keys)
200
+ wrapped do
201
+ @client.call [:mget, *keys]
202
+ end
203
+ end
204
+
205
+ # Append a value to a key.
206
+ def append(key, value)
207
+ wrapped do
208
+ @client.call [:append, key, value]
209
+ end
210
+ end
211
+
212
+ def substr(key, start, stop)
213
+ wrapped do
214
+ @client.call [:substr, key, start, stop]
215
+ end
216
+ end
217
+
218
+ # Get the length of the value stored in a key.
219
+ def strlen(key)
220
+ wrapped do
221
+ @client.call [:strlen, key]
222
+ end
223
+ end
224
+
225
+ # Get all the fields and values in a hash.
226
+ def hgetall(key)
227
+ wrapped do
228
+ reply = @client.call [:hgetall, key]
229
+
230
+ if reply.kind_of?(Array)
231
+ Hash[*reply]
232
+ else
233
+ reply
234
+ end
235
+ end
236
+ end
237
+
238
+ # Get the value of a hash field.
239
+ def hget(key, field)
240
+ wrapped do
241
+ @client.call [:hget, key, field]
242
+ end
243
+ end
244
+
245
+ # Delete a hash field.
246
+ def hdel(key, field)
247
+ wrapped do
248
+ @client.call [:hdel, key, field]
249
+ end
250
+ end
251
+
252
+ # Get all the fields in a hash.
253
+ def hkeys(key)
254
+ wrapped do
255
+ @client.call [:hkeys, key]
256
+ end
257
+ end
258
+
259
+ # Find all keys matching the given pattern.
260
+ def keys(pattern = "*")
261
+ wrapped do
262
+ reply = @client.call [:keys, pattern]
263
+
264
+ if reply.kind_of?(String)
265
+ reply.split(" ")
266
+ else
267
+ reply
268
+ end
269
+ end
270
+ end
271
+
272
+ # Return a random key from the keyspace.
273
+ def randomkey
274
+ wrapped do
275
+ @client.call [:randomkey]
276
+ end
277
+ end
278
+
279
+ # Echo the given string.
280
+ def echo(value)
281
+ wrapped do
282
+ @client.call [:echo, value]
283
+ end
284
+ end
285
+
286
+ # Ping the server.
287
+ def ping
288
+ wrapped do
289
+ @client.call [:ping]
290
+ end
291
+ end
292
+
293
+ # Get the UNIX time stamp of the last successful save to disk.
294
+ def lastsave
295
+ wrapped do
296
+ @client.call [:lastsave]
297
+ end
298
+ end
299
+
300
+ # Return the number of keys in the selected database.
301
+ def dbsize
302
+ wrapped do
303
+ @client.call [:dbsize]
304
+ end
305
+ end
306
+
307
+ # Determine if a key exists.
308
+ def exists(key)
309
+ wrapped do
310
+ _bool @client.call [:exists, key]
311
+ end
312
+ end
313
+
314
+ # Get the length of a list.
315
+ def llen(key)
316
+ wrapped do
317
+ @client.call [:llen, key]
318
+ end
319
+ end
320
+
321
+ # Get a range of elements from a list.
322
+ def lrange(key, start, stop)
323
+ wrapped do
324
+ @client.call [:lrange, key, start, stop]
325
+ end
326
+ end
327
+
328
+ # Trim a list to the specified range.
329
+ def ltrim(key, start, stop)
330
+ wrapped do
331
+ @client.call [:ltrim, key, start, stop]
332
+ end
333
+ end
334
+
335
+ # Get an element from a list by its index.
336
+ def lindex(key, index)
337
+ wrapped do
338
+ @client.call [:lindex, key, index]
339
+ end
340
+ end
341
+
342
+ # Insert an element before or after another element in a list.
343
+ def linsert(key, where, pivot, value)
344
+ wrapped do
345
+ @client.call [:linsert, key, where, pivot, value]
346
+ end
347
+ end
348
+
349
+ # Set the value of an element in a list by its index.
350
+ def lset(key, index, value)
351
+ wrapped do
352
+ @client.call [:lset, key, index, value]
353
+ end
354
+ end
355
+
356
+ # Remove elements from a list.
357
+ def lrem(key, count, value)
358
+ wrapped do
359
+ @client.call [:lrem, key, count, value]
360
+ end
361
+ end
362
+
363
+ # Append a value to a list.
364
+ def rpush(key, value)
365
+ wrapped do
366
+ @client.call [:rpush, key, value]
367
+ end
368
+ end
369
+
370
+ # Append a value to a list, only if the list exists.
371
+ def rpushx(key, value)
372
+ wrapped do
373
+ @client.call [:rpushx, key, value]
374
+ end
375
+ end
376
+
377
+ # Prepend a value to a list.
378
+ def lpush(key, value)
379
+ wrapped do
380
+ @client.call [:lpush, key, value]
381
+ end
382
+ end
383
+
384
+ # Prepend a value to a list, only if the list exists.
385
+ def lpushx(key, value)
386
+ wrapped do
387
+ @client.call [:lpushx, key, value]
388
+ end
389
+ end
390
+
391
+ # Remove and get the last element in a list.
392
+ def rpop(key)
393
+ wrapped do
394
+ @client.call [:rpop, key]
395
+ end
396
+ end
397
+
398
+ # Remove and get the first element in a list, or block until one is available.
399
+ def blpop(*args)
400
+ wrapped do
401
+ @client.call_without_timeout(:blpop, *args)
402
+ end
403
+ end
404
+
405
+ # Remove and get the last element in a list, or block until one is available.
406
+ def brpop(*args)
407
+ wrapped do
408
+ @client.call_without_timeout(:brpop, *args)
409
+ end
410
+ end
411
+
412
+ # Pop a value from a list, push it to another list and return it; or block
413
+ # until one is available.
414
+ def brpoplpush(source, destination, timeout)
415
+ wrapped do
416
+ @client.call_without_timeout(:brpoplpush, source, destination, timeout)
417
+ end
418
+ end
419
+
420
+ # Remove the last element in a list, append it to another list and return it.
421
+ def rpoplpush(source, destination)
422
+ wrapped do
423
+ @client.call [:rpoplpush, source, destination]
424
+ end
425
+ end
426
+
427
+ # Remove and get the first element in a list.
428
+ def lpop(key)
429
+ wrapped do
430
+ @client.call [:lpop, key]
431
+ end
432
+ end
433
+
434
+ # Get all the members in a set.
435
+ def smembers(key)
436
+ wrapped do
437
+ @client.call [:smembers, key]
438
+ end
439
+ end
440
+
441
+ # Determine if a given value is a member of a set.
442
+ def sismember(key, member)
443
+ wrapped do
444
+ _bool @client.call [:sismember, key, member]
445
+ end
446
+ end
447
+
448
+ # Add a member to a set.
449
+ def sadd(key, value)
450
+ wrapped do
451
+ _bool @client.call [:sadd, key, value]
452
+ end
453
+ end
454
+
455
+ # Remove a member from a set.
456
+ def srem(key, value)
457
+ wrapped do
458
+ _bool @client.call [:srem, key, value]
459
+ end
460
+ end
461
+
462
+ # Move a member from one set to another.
463
+ def smove(source, destination, member)
464
+ wrapped do
465
+ _bool @client.call [:smove, source, destination, member]
466
+ end
467
+ end
468
+
469
+ # Remove and return a random member from a set.
470
+ def spop(key)
471
+ wrapped do
472
+ @client.call [:spop, key]
473
+ end
474
+ end
475
+
476
+ # Get the number of members in a set.
477
+ def scard(key)
478
+ wrapped do
479
+ @client.call [:scard, key]
480
+ end
481
+ end
482
+
483
+ # Intersect multiple sets.
484
+ def sinter(*keys)
485
+ wrapped do
486
+ @client.call [:sinter, *keys]
487
+ end
488
+ end
489
+
490
+ # Intersect multiple sets and store the resulting set in a key.
491
+ def sinterstore(destination, *keys)
492
+ wrapped do
493
+ @client.call [:sinterstore, destination, *keys]
494
+ end
495
+ end
496
+
497
+ # Add multiple sets.
498
+ def sunion(*keys)
499
+ wrapped do
500
+ @client.call [:sunion, *keys]
501
+ end
502
+ end
503
+
504
+ # Add multiple sets and store the resulting set in a key.
505
+ def sunionstore(destination, *keys)
506
+ wrapped do
507
+ @client.call [:sunionstore, destination, *keys]
508
+ end
509
+ end
510
+
511
+ # Subtract multiple sets.
512
+ def sdiff(*keys)
513
+ wrapped do
514
+ @client.call [:sdiff, *keys]
515
+ end
516
+ end
517
+
518
+ # Subtract multiple sets and store the resulting set in a key.
519
+ def sdiffstore(destination, *keys)
520
+ wrapped do
521
+ @client.call [:sdiffstore, destination, *keys]
522
+ end
523
+ end
524
+
525
+ # Get a random member from a set.
526
+ def srandmember(key)
527
+ wrapped do
528
+ @client.call [:srandmember, key]
529
+ end
530
+ end
531
+
532
+ # Add a member to a sorted set, or update its score if it already exists.
533
+ def zadd(key, score, member)
534
+ wrapped do
535
+ _bool @client.call [:zadd, key, score, member]
536
+ end
537
+ end
538
+
539
+ # Determine the index of a member in a sorted set.
540
+ def zrank(key, member)
541
+ wrapped do
542
+ @client.call [:zrank, key, member]
543
+ end
544
+ end
545
+
546
+ # Determine the index of a member in a sorted set, with scores ordered from
547
+ # high to low.
548
+ def zrevrank(key, member)
549
+ wrapped do
550
+ @client.call [:zrevrank, key, member]
551
+ end
552
+ end
553
+
554
+ # Increment the score of a member in a sorted set.
555
+ def zincrby(key, increment, member)
556
+ wrapped do
557
+ @client.call [:zincrby, key, increment, member]
558
+ end
559
+ end
560
+
561
+ # Get the number of members in a sorted set.
562
+ def zcard(key)
563
+ wrapped do
564
+ @client.call [:zcard, key]
565
+ end
566
+ end
567
+
568
+ # Return a range of members in a sorted set, by index.
569
+ def zrange(key, start, stop, options = {})
570
+ command = CommandOptions.new(options) do |c|
571
+ c.bool :withscores
572
+ c.bool :with_scores
573
+ end
574
+
575
+ wrapped do
576
+ @client.call [:zrange, key, start, stop, *command.to_a]
577
+ end
578
+ end
579
+
580
+ # Return a range of members in a sorted set, by score.
581
+ def zrangebyscore(key, min, max, options = {})
582
+ command = CommandOptions.new(options) do |c|
583
+ c.splat :limit
584
+ c.bool :withscores
585
+ c.bool :with_scores
586
+ end
587
+
588
+ wrapped do
589
+ @client.call [:zrangebyscore, key, min, max, *command.to_a]
590
+ end
591
+ end
592
+
593
+ # Count the members in a sorted set with scores within the given values.
594
+ def zcount(key, start, stop)
595
+ wrapped do
596
+ @client.call [:zcount, key, start, stop]
597
+ end
598
+ end
599
+
600
+ # Return a range of members in a sorted set, by index, with scores ordered
601
+ # from high to low.
602
+ def zrevrange(key, start, stop, options = {})
603
+ command = CommandOptions.new(options) do |c|
604
+ c.bool :withscores
605
+ c.bool :with_scores
606
+ end
607
+
608
+ wrapped do
609
+ @client.call [:zrevrange, key, start, stop, *command.to_a]
610
+ end
611
+ end
612
+
613
+ # Return a range of members in a sorted set, by score, with scores ordered
614
+ # from high to low.
615
+ def zrevrangebyscore(key, max, min, options = {})
616
+ command = CommandOptions.new(options) do |c|
617
+ c.splat :limit
618
+ c.bool :withscores
619
+ c.bool :with_scores
620
+ end
621
+
622
+ wrapped do
623
+ @client.call [:zrevrangebyscore, key, max, min, *command.to_a]
624
+ end
625
+ end
626
+
627
+ # Remove all members in a sorted set within the given scores.
628
+ def zremrangebyscore(key, min, max)
629
+ wrapped do
630
+ @client.call [:zremrangebyscore, key, min, max]
631
+ end
632
+ end
633
+
634
+ # Remove all members in a sorted set within the given indexes.
635
+ def zremrangebyrank(key, start, stop)
636
+ wrapped do
637
+ @client.call [:zremrangebyrank, key, start, stop]
638
+ end
639
+ end
640
+
641
+ # Get the score associated with the given member in a sorted set.
642
+ def zscore(key, member)
643
+ wrapped do
644
+ @client.call [:zscore, key, member]
645
+ end
646
+ end
647
+
648
+ # Remove a member from a sorted set.
649
+ def zrem(key, member)
650
+ wrapped do
651
+ _bool @client.call [:zrem, key, member]
652
+ end
653
+ end
654
+
655
+ # Intersect multiple sorted sets and store the resulting sorted set in a new
656
+ # key.
657
+ def zinterstore(destination, keys, options = {})
658
+ command = CommandOptions.new(options) do |c|
659
+ c.splat :weights
660
+ c.value :aggregate
661
+ end
662
+
663
+ wrapped do
664
+ @client.call [:zinterstore, destination, keys.size, *(keys + command.to_a)]
665
+ end
666
+ end
667
+
668
+ # Add multiple sorted sets and store the resulting sorted set in a new key.
669
+ def zunionstore(destination, keys, options = {})
670
+ command = CommandOptions.new(options) do |c|
671
+ c.splat :weights
672
+ c.value :aggregate
673
+ end
674
+
675
+ wrapped do
676
+ @client.call [:zunionstore, destination, keys.size, *(keys + command.to_a)]
677
+ end
678
+ end
679
+
680
+ # Move a key to another database.
681
+ def move(key, db)
682
+ wrapped do
683
+ _bool @client.call [:move, key, db]
684
+ end
685
+ end
686
+
687
+ # Set the value of a key, only if the key does not exist.
688
+ def setnx(key, value)
689
+ wrapped do
690
+ _bool @client.call [:setnx, key, value]
691
+ end
692
+ end
693
+
694
+ # Delete a key.
695
+ def del(*keys)
696
+ wrapped do
697
+ @client.call [:del, *keys]
698
+ end
699
+ end
700
+
701
+ # Rename a key.
702
+ def rename(old_name, new_name)
703
+ wrapped do
704
+ @client.call [:rename, old_name, new_name]
705
+ end
706
+ end
707
+
708
+ # Rename a key, only if the new key does not exist.
709
+ def renamenx(old_name, new_name)
710
+ wrapped do
711
+ _bool @client.call [:renamenx, old_name, new_name]
712
+ end
713
+ end
714
+
715
+ # Set a key's time to live in seconds.
716
+ def expire(key, seconds)
717
+ wrapped do
718
+ _bool @client.call [:expire, key, seconds]
719
+ end
720
+ end
721
+
722
+ # Remove the expiration from a key.
723
+ def persist(key)
724
+ wrapped do
725
+ _bool @client.call [:persist, key]
726
+ end
727
+ end
728
+
729
+ # Get the time to live for a key.
730
+ def ttl(key)
731
+ wrapped do
732
+ @client.call [:ttl, key]
733
+ end
734
+ end
735
+
736
+ # Set the expiration for a key as a UNIX timestamp.
737
+ def expireat(key, unix_time)
738
+ wrapped do
739
+ _bool @client.call [:expireat, key, unix_time]
740
+ end
741
+ end
742
+
743
+ # Set the string value of a hash field.
744
+ def hset(key, field, value)
745
+ wrapped do
746
+ _bool @client.call [:hset, key, field, value]
747
+ end
748
+ end
749
+
750
+ # Set the value of a hash field, only if the field does not exist.
751
+ def hsetnx(key, field, value)
752
+ wrapped do
753
+ _bool @client.call [:hsetnx, key, field, value]
754
+ end
755
+ end
756
+
757
+ # Set multiple hash fields to multiple values.
758
+ def hmset(key, *attrs)
759
+ wrapped do
760
+ @client.call [:hmset, key, *attrs]
761
+ end
762
+ end
763
+
764
+ def mapped_hmset(key, hash)
765
+ hmset(key, *hash.to_a.flatten)
766
+ end
767
+
768
+ # Get the values of all the given hash fields.
769
+ def hmget(key, *fields)
770
+ wrapped do
771
+ @client.call [:hmget, key, *fields]
772
+ end
773
+ end
774
+
775
+ def mapped_hmget(key, *fields)
776
+ reply = hmget(key, *fields)
777
+
778
+ if reply.kind_of?(Array)
779
+ Hash[*fields.zip(reply).flatten]
780
+ else
781
+ reply
782
+ end
783
+ end
784
+
785
+ # Get the number of fields in a hash.
786
+ def hlen(key)
787
+ wrapped do
788
+ @client.call [:hlen, key]
789
+ end
790
+ end
791
+
792
+ # Get all the values in a hash.
793
+ def hvals(key)
794
+ wrapped do
795
+ @client.call [:hvals, key]
796
+ end
797
+ end
798
+
799
+ # Increment the integer value of a hash field by the given number.
800
+ def hincrby(key, field, increment)
801
+ wrapped do
802
+ @client.call [:hincrby, key, field, increment]
803
+ end
804
+ end
805
+
806
+ # Discard all commands issued after MULTI.
807
+ def discard
808
+ wrapped do
809
+ @client.call [:discard]
810
+ end
811
+ end
812
+
813
+ # Determine if a hash field exists.
814
+ def hexists(key, field)
815
+ wrapped do
816
+ _bool @client.call [:hexists, key, field]
817
+ end
818
+ end
819
+
820
+ # Listen for all requests received by the server in real time.
821
+ def monitor(&block)
822
+ wrapped do
823
+ @client.call_loop([:monitor], &block)
824
+ end
825
+ end
826
+
827
+ def debug(*args)
828
+ wrapped do
829
+ @client.call [:debug, *args]
830
+ end
831
+ end
832
+
833
+ def object(*args)
834
+ wrapped do
835
+ @client.call [:object, *args]
836
+ end
837
+ end
838
+
839
+ # Internal command used for replication.
840
+ def sync
841
+ wrapped do
842
+ @client.call [:sync]
843
+ end
844
+ end
845
+
846
+ def [](key)
847
+ get(key)
848
+ end
849
+
850
+ def []=(key,value)
851
+ set(key, value)
852
+ end
853
+
854
+ # Set the string value of a key.
855
+ def set(key, value)
856
+ wrapped do
857
+ @client.call [:set, key, value]
858
+ end
859
+ end
860
+
861
+ # Sets or clears the bit at offset in the string value stored at key.
862
+ def setbit(key, offset, value)
863
+ wrapped do
864
+ @client.call [:setbit, key, offset, value]
865
+ end
866
+ end
867
+
868
+ # Set the value and expiration of a key.
869
+ def setex(key, ttl, value)
870
+ wrapped do
871
+ @client.call [:setex, key, ttl, value]
872
+ end
873
+ end
874
+
875
+ # Overwrite part of a string at key starting at the specified offset.
876
+ def setrange(key, offset, value)
877
+ wrapped do
878
+ @client.call [:setrange, key, offset, value]
879
+ end
880
+ end
881
+
882
+ # Set multiple keys to multiple values.
883
+ def mset(*args)
884
+ wrapped do
885
+ @client.call [:mset, *args]
886
+ end
887
+ end
888
+
889
+ def mapped_mset(hash)
890
+ mset(*hash.to_a.flatten)
891
+ end
892
+
893
+ # Set multiple keys to multiple values, only if none of the keys exist.
894
+ def msetnx(*args)
895
+ wrapped do
896
+ @client.call [:msetnx, *args]
897
+ end
898
+ end
899
+
900
+ def mapped_msetnx(hash)
901
+ msetnx(*hash.to_a.flatten)
902
+ end
903
+
904
+ def mapped_mget(*keys)
905
+ reply = mget(*keys)
906
+
907
+ if reply.kind_of?(Array)
908
+ Hash[*keys.zip(reply).flatten]
909
+ else
910
+ reply
911
+ end
912
+ end
913
+
914
+ # Sort the elements in a list, set or sorted set.
915
+ def sort(key, options = {})
916
+ command = CommandOptions.new(options) do |c|
917
+ c.value :by
918
+ c.splat :limit
919
+ c.multi :get
920
+ c.words :order
921
+ c.value :store
922
+ end
923
+
924
+ wrapped do
925
+ @client.call [:sort, key, *command.to_a]
926
+ end
927
+ end
928
+
929
+ # Increment the integer value of a key by one.
930
+ def incr(key)
931
+ wrapped do
932
+ @client.call [:incr, key]
933
+ end
934
+ end
935
+
936
+ # Increment the integer value of a key by the given number.
937
+ def incrby(key, increment)
938
+ wrapped do
939
+ @client.call [:incrby, key, increment]
940
+ end
941
+ end
942
+
943
+ # Decrement the integer value of a key by one.
944
+ def decr(key)
945
+ wrapped do
946
+ @client.call [:decr, key]
947
+ end
948
+ end
949
+
950
+ # Decrement the integer value of a key by the given number.
951
+ def decrby(key, decrement)
952
+ wrapped do
953
+ @client.call [:decrby, key, decrement]
954
+ end
955
+ end
956
+
957
+ # Determine the type stored at key.
958
+ def type(key)
959
+ wrapped do
960
+ @client.call [:type, key]
961
+ end
962
+ end
963
+
964
+ # Close the connection.
965
+ def quit
966
+ wrapped do
967
+ begin
968
+ @client.call [:quit]
969
+ rescue Errno::ECONNRESET
970
+ ensure
971
+ @client.disconnect
972
+ end
973
+ end
974
+ end
975
+
976
+ # Synchronously save the dataset to disk and then shut down the server.
977
+ def shutdown
978
+ wrapped do
979
+ @client.call_without_reply [:shutdown]
980
+ end
981
+ end
982
+
983
+ # Make the server a slave of another instance, or promote it as master.
984
+ def slaveof(host, port)
985
+ wrapped do
986
+ @client.call [:slaveof, host, port]
987
+ end
988
+ end
989
+
990
+ def pipelined(options = {})
991
+ wrapped do
992
+ begin
993
+ original, @client = @client, Pipeline.new
994
+ yield
995
+ original.call_pipelined(@client.commands, options) unless @client.commands.empty?
996
+ ensure
997
+ @client = original
998
+ end
999
+ end
1000
+ end
1001
+
1002
+ # Watch the given keys to determine execution of the MULTI/EXEC block.
1003
+ def watch(*keys)
1004
+ wrapped do
1005
+ @client.call [:watch, *keys]
1006
+ end
1007
+ end
1008
+
1009
+ # Forget about all watched keys.
1010
+ def unwatch
1011
+ wrapped do
1012
+ @client.call [:unwatch]
1013
+ end
1014
+ end
1015
+
1016
+ # Execute all commands issued after MULTI.
1017
+ def exec
1018
+ wrapped do
1019
+ @client.call [:exec]
1020
+ end
1021
+ end
1022
+
1023
+ # Mark the start of a transaction block.
1024
+ def multi
1025
+ wrapped do
1026
+ if !block_given?
1027
+ @client.call :multi
1028
+ else
1029
+ result = pipelined(:raise => false) do
1030
+ multi
1031
+ yield(self)
1032
+ exec
1033
+ end
1034
+
1035
+ result.last
1036
+ end
1037
+ end
1038
+ end
1039
+
1040
+ # Post a message to a channel.
1041
+ def publish(channel, message)
1042
+ wrapped do
1043
+ @client.call [:publish, channel, message]
1044
+ end
1045
+ end
1046
+
1047
+ def subscribed?
1048
+ wrapped do
1049
+ @client.kind_of? SubscribedClient
1050
+ end
1051
+ end
1052
+
1053
+ # Stop listening for messages posted to the given channels.
1054
+ def unsubscribe(*channels)
1055
+ wrapped do
1056
+ raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
1057
+ @client.unsubscribe(*channels)
1058
+ end
1059
+ end
1060
+
1061
+ # Stop listening for messages posted to channels matching the given patterns.
1062
+ def punsubscribe(*channels)
1063
+ wrapped do
1064
+ raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
1065
+ @client.punsubscribe(*channels)
1066
+ end
1067
+ end
1068
+
1069
+ # Listen for messages published to the given channels.
1070
+ def subscribe(*channels, &block)
1071
+ wrapped do
1072
+ subscription(:subscribe, channels, block)
1073
+ end
1074
+ end
1075
+
1076
+ # Listen for messages published to channels matching the given patterns.
1077
+ def psubscribe(*channels, &block)
1078
+ wrapped do
1079
+ subscription(:psubscribe, channels, block)
1080
+ end
1081
+ end
1082
+
1083
+ def id
1084
+ wrapped do
1085
+ @client.id
1086
+ end
1087
+ end
1088
+
1089
+ def inspect
1090
+ wrapped do
1091
+ "#<Redis client v#{Redis::VERSION} connected to #{id} (Redis v#{info["redis_version"]})>"
1092
+ end
1093
+ end
1094
+
1095
+ def method_missing(command, *args)
1096
+ wrapped do
1097
+ @client.call [command, *args]
1098
+ end
1099
+ end
1100
+
1101
+ class CommandOptions
1102
+ def initialize(options)
1103
+ @result = []
1104
+ @options = options
1105
+ yield(self)
1106
+ end
1107
+
1108
+ def bool(name)
1109
+ insert(name) { |argument, value| [argument] }
1110
+ end
1111
+
1112
+ def value(name)
1113
+ insert(name) { |argument, value| [argument, value] }
1114
+ end
1115
+
1116
+ def splat(name)
1117
+ insert(name) { |argument, value| [argument, *value] }
1118
+ end
1119
+
1120
+ def multi(name)
1121
+ insert(name) { |argument, value| [argument].product(Array(value)).flatten }
1122
+ end
1123
+
1124
+ def words(name)
1125
+ insert(name) { |argument, value| value.split(" ") }
1126
+ end
1127
+
1128
+ def to_a
1129
+ @result
1130
+ end
1131
+
1132
+ def insert(name)
1133
+ @result += yield(name.to_s.upcase.gsub("_", ""), @options[name]) if @options[name]
1134
+ end
1135
+ end
1136
+
1137
+ private
1138
+
1139
+ # Commands returning 1 for true and 0 for false may be executed in a pipeline
1140
+ # where the method call will return nil. Propagate the nil instead of falsely
1141
+ # returning false.
1142
+ def _bool(value)
1143
+ value == 1 if value
1144
+ end
1145
+
1146
+ def subscription(method, channels, block)
1147
+ return @client.call [method, *channels] if subscribed?
1148
+
1149
+ begin
1150
+ original, @client = @client, SubscribedClient.new(@client)
1151
+ @client.send(method, *channels, &block)
1152
+ ensure
1153
+ @client = original
1154
+ end
1155
+ end
1156
+
1157
+ end
1158
+
1159
+ require "redis/version"
1160
+ require "redis/connection"
1161
+ require "redis/client"
1162
+ require "redis/pipeline"
1163
+ require "redis/subscribe"
1164
+ require "redis/compat"
1165
+ require "redis/distributed"
1166
+ require "redis/hash_ring"