yam-redis-with-retries 2.2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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"