redis 3.0.0 → 4.5.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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +315 -0
  3. data/README.md +301 -58
  4. data/lib/redis/client.rb +383 -88
  5. data/lib/redis/cluster/command.rb +81 -0
  6. data/lib/redis/cluster/command_loader.rb +33 -0
  7. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  8. data/lib/redis/cluster/node.rb +108 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +37 -0
  11. data/lib/redis/cluster/option.rb +93 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +49 -0
  14. data/lib/redis/cluster.rb +291 -0
  15. data/lib/redis/connection/command_helper.rb +7 -10
  16. data/lib/redis/connection/hiredis.rb +12 -8
  17. data/lib/redis/connection/registry.rb +2 -1
  18. data/lib/redis/connection/ruby.rb +266 -74
  19. data/lib/redis/connection/synchrony.rb +41 -14
  20. data/lib/redis/connection.rb +4 -2
  21. data/lib/redis/distributed.rb +258 -76
  22. data/lib/redis/errors.rb +48 -0
  23. data/lib/redis/hash_ring.rb +31 -73
  24. data/lib/redis/pipeline.rb +74 -18
  25. data/lib/redis/subscribe.rb +24 -13
  26. data/lib/redis/version.rb +3 -1
  27. data/lib/redis.rb +2068 -464
  28. metadata +63 -160
  29. data/.gitignore +0 -10
  30. data/.order +0 -169
  31. data/.travis/Gemfile +0 -11
  32. data/.travis.yml +0 -50
  33. data/.yardopts +0 -3
  34. data/Rakefile +0 -392
  35. data/benchmarking/logging.rb +0 -62
  36. data/benchmarking/pipeline.rb +0 -51
  37. data/benchmarking/speed.rb +0 -21
  38. data/benchmarking/suite.rb +0 -24
  39. data/benchmarking/worker.rb +0 -71
  40. data/examples/basic.rb +0 -15
  41. data/examples/dist_redis.rb +0 -43
  42. data/examples/incr-decr.rb +0 -17
  43. data/examples/list.rb +0 -26
  44. data/examples/pubsub.rb +0 -31
  45. data/examples/sets.rb +0 -36
  46. data/examples/unicorn/config.ru +0 -3
  47. data/examples/unicorn/unicorn.rb +0 -20
  48. data/redis.gemspec +0 -41
  49. data/test/blocking_commands_test.rb +0 -42
  50. data/test/command_map_test.rb +0 -30
  51. data/test/commands_on_hashes_test.rb +0 -21
  52. data/test/commands_on_lists_test.rb +0 -20
  53. data/test/commands_on_sets_test.rb +0 -77
  54. data/test/commands_on_sorted_sets_test.rb +0 -109
  55. data/test/commands_on_strings_test.rb +0 -83
  56. data/test/commands_on_value_types_test.rb +0 -99
  57. data/test/connection_handling_test.rb +0 -189
  58. data/test/db/.gitignore +0 -1
  59. data/test/distributed_blocking_commands_test.rb +0 -46
  60. data/test/distributed_commands_on_hashes_test.rb +0 -10
  61. data/test/distributed_commands_on_lists_test.rb +0 -22
  62. data/test/distributed_commands_on_sets_test.rb +0 -83
  63. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  64. data/test/distributed_commands_on_strings_test.rb +0 -48
  65. data/test/distributed_commands_on_value_types_test.rb +0 -87
  66. data/test/distributed_commands_requiring_clustering_test.rb +0 -148
  67. data/test/distributed_connection_handling_test.rb +0 -23
  68. data/test/distributed_internals_test.rb +0 -15
  69. data/test/distributed_key_tags_test.rb +0 -52
  70. data/test/distributed_persistence_control_commands_test.rb +0 -26
  71. data/test/distributed_publish_subscribe_test.rb +0 -92
  72. data/test/distributed_remote_server_control_commands_test.rb +0 -53
  73. data/test/distributed_scripting_test.rb +0 -102
  74. data/test/distributed_sorting_test.rb +0 -20
  75. data/test/distributed_test.rb +0 -58
  76. data/test/distributed_transactions_test.rb +0 -32
  77. data/test/encoding_test.rb +0 -18
  78. data/test/error_replies_test.rb +0 -59
  79. data/test/helper.rb +0 -188
  80. data/test/helper_test.rb +0 -22
  81. data/test/internals_test.rb +0 -214
  82. data/test/lint/blocking_commands.rb +0 -124
  83. data/test/lint/hashes.rb +0 -162
  84. data/test/lint/lists.rb +0 -143
  85. data/test/lint/sets.rb +0 -96
  86. data/test/lint/sorted_sets.rb +0 -201
  87. data/test/lint/strings.rb +0 -157
  88. data/test/lint/value_types.rb +0 -106
  89. data/test/persistence_control_commands_test.rb +0 -26
  90. data/test/pipelining_commands_test.rb +0 -195
  91. data/test/publish_subscribe_test.rb +0 -153
  92. data/test/remote_server_control_commands_test.rb +0 -104
  93. data/test/scripting_test.rb +0 -78
  94. data/test/sorting_test.rb +0 -45
  95. data/test/support/connection/hiredis.rb +0 -1
  96. data/test/support/connection/ruby.rb +0 -1
  97. data/test/support/connection/synchrony.rb +0 -17
  98. data/test/support/redis_mock.rb +0 -92
  99. data/test/support/wire/synchrony.rb +0 -24
  100. data/test/support/wire/thread.rb +0 -5
  101. data/test/synchrony_driver.rb +0 -57
  102. data/test/test.conf +0 -9
  103. data/test/thread_safety_test.rb +0 -32
  104. data/test/transactions_test.rb +0 -244
  105. data/test/unknown_commands_test.rb +0 -14
  106. data/test/url_param_test.rb +0 -64
@@ -1,37 +1,47 @@
1
- require "redis/hash_ring"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hash_ring"
2
4
 
3
5
  class Redis
4
6
  class Distributed
5
-
6
7
  class CannotDistribute < RuntimeError
7
8
  def initialize(command)
8
9
  @command = command
9
10
  end
10
11
 
11
12
  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
+ "#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need " \
14
+ "to be on the same server or because we cannot guarantee that the operation will be atomic."
13
15
  end
14
16
  end
15
17
 
16
18
  attr_reader :ring
17
19
 
18
- def initialize(urls, options = {})
19
- @tag = options.delete(:tag) || /^\{(.+?)\}/
20
- @default_options = options
21
- @ring = HashRing.new urls.map { |url| Redis.new(options.merge(:url => url)) }
20
+ def initialize(node_configs, options = {})
21
+ @tag = options[:tag] || /^\{(.+?)\}/
22
+ @ring = options[:ring] || HashRing.new
23
+ @node_configs = node_configs.dup
24
+ @default_options = options.dup
25
+ node_configs.each { |node_config| add_node(node_config) }
22
26
  @subscribed_node = nil
27
+ @watch_key = nil
23
28
  end
24
29
 
25
30
  def node_for(key)
26
- @ring.get_node(key_tag(key.to_s) || key.to_s)
31
+ key = key_tag(key.to_s) || key.to_s
32
+ raise CannotDistribute, :watch if @watch_key && @watch_key != key
33
+
34
+ @ring.get_node(key)
27
35
  end
28
36
 
29
37
  def nodes
30
38
  @ring.nodes
31
39
  end
32
40
 
33
- def add_node(url)
34
- @ring.add_node Redis.new(@default_options.merge(:url => url))
41
+ def add_node(options)
42
+ options = { url: options } if options.is_a?(String)
43
+ options = @default_options.merge(options)
44
+ @ring.add_node Redis.new(options)
35
45
  end
36
46
 
37
47
  # Change the selected database for the current connection.
@@ -134,6 +144,21 @@ class Redis
134
144
  node_for(key).pttl(key)
135
145
  end
136
146
 
147
+ # Return a serialized version of the value stored at a key.
148
+ def dump(key)
149
+ node_for(key).dump(key)
150
+ end
151
+
152
+ # Create a key using the serialized value, previously obtained using DUMP.
153
+ def restore(key, ttl, serialized_value, **options)
154
+ node_for(key).restore(key, ttl, serialized_value, **options)
155
+ end
156
+
157
+ # Transfer a key from the connected instance to another instance.
158
+ def migrate(_key, _options)
159
+ raise CannotDistribute, :migrate
160
+ end
161
+
137
162
  # Delete a key.
138
163
  def del(*args)
139
164
  keys_per_node = args.group_by { |key| node_for(key) }
@@ -142,9 +167,42 @@ class Redis
142
167
  end
143
168
  end
144
169
 
170
+ # Unlink keys.
171
+ def unlink(*args)
172
+ keys_per_node = args.group_by { |key| node_for(key) }
173
+ keys_per_node.inject(0) do |sum, (node, keys)|
174
+ sum + node.unlink(*keys)
175
+ end
176
+ end
177
+
145
178
  # Determine if a key exists.
146
- def exists(key)
147
- node_for(key).exists(key)
179
+ def exists(*args)
180
+ if !Redis.exists_returns_integer && args.size == 1
181
+ message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
182
+ "use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
183
+ "(#{::Kernel.caller(1, 1).first})\n"
184
+
185
+ if defined?(::Warning)
186
+ ::Warning.warn(message)
187
+ else
188
+ warn(message)
189
+ end
190
+ exists?(*args)
191
+ else
192
+ keys_per_node = args.group_by { |key| node_for(key) }
193
+ keys_per_node.inject(0) do |sum, (node, keys)|
194
+ sum + node._exists(*keys)
195
+ end
196
+ end
197
+ end
198
+
199
+ # Determine if any of the keys exists.
200
+ def exists?(*args)
201
+ keys_per_node = args.group_by { |key| node_for(key) }
202
+ keys_per_node.each do |node, keys|
203
+ return true if node.exists?(*keys)
204
+ end
205
+ false
148
206
  end
149
207
 
150
208
  # Find all keys matching the given pattern.
@@ -177,11 +235,11 @@ class Redis
177
235
  end
178
236
 
179
237
  # Sort the elements in a list, set or sorted set.
180
- def sort(key, options = {})
238
+ def sort(key, **options)
181
239
  keys = [key, options[:by], options[:store], *Array(options[:get])].compact
182
240
 
183
241
  ensure_same_node(:sort, keys) do |node|
184
- node.sort(key, options)
242
+ node.sort(key, **options)
185
243
  end
186
244
  end
187
245
 
@@ -216,8 +274,8 @@ class Redis
216
274
  end
217
275
 
218
276
  # Set the string value of a key.
219
- def set(key, value)
220
- node_for(key).set(key, value)
277
+ def set(key, value, **options)
278
+ node_for(key).set(key, value, **options)
221
279
  end
222
280
 
223
281
  # Set the time to live in seconds of a key.
@@ -236,20 +294,20 @@ class Redis
236
294
  end
237
295
 
238
296
  # Set multiple keys to multiple values.
239
- def mset(*args)
297
+ def mset(*_args)
240
298
  raise CannotDistribute, :mset
241
299
  end
242
300
 
243
- def mapped_mset(hash)
301
+ def mapped_mset(_hash)
244
302
  raise CannotDistribute, :mapped_mset
245
303
  end
246
304
 
247
305
  # Set multiple keys to multiple values, only if none of the keys exist.
248
- def msetnx(*args)
306
+ def msetnx(*_args)
249
307
  raise CannotDistribute, :msetnx
250
308
  end
251
309
 
252
- def mapped_msetnx(hash)
310
+ def mapped_msetnx(_hash)
253
311
  raise CannotDistribute, :mapped_msetnx
254
312
  end
255
313
 
@@ -258,13 +316,26 @@ class Redis
258
316
  node_for(key).get(key)
259
317
  end
260
318
 
261
- # Get the values of all the given keys.
319
+ # Get the value of a key and delete it.
320
+ def getdel(key)
321
+ node_for(key).getdel(key)
322
+ end
323
+
324
+ # Get the value of a key and sets its time to live based on options.
325
+ def getex(key, **options)
326
+ node_for(key).getex(key, **options)
327
+ end
328
+
329
+ # Get the values of all the given keys as an Array.
262
330
  def mget(*keys)
263
- raise CannotDistribute, :mget
331
+ mapped_mget(*keys).values_at(*keys)
264
332
  end
265
333
 
334
+ # Get the values of all the given keys as a Hash.
266
335
  def mapped_mget(*keys)
267
- raise CannotDistribute, :mapped_mget
336
+ keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
337
+ results.merge! node.mapped_mget(*subkeys)
338
+ end
268
339
  end
269
340
 
270
341
  # Overwrite part of a string at key starting at the specified offset.
@@ -292,6 +363,23 @@ class Redis
292
363
  node_for(key).append(key, value)
293
364
  end
294
365
 
366
+ # Count the number of set bits in a range of the string value stored at key.
367
+ def bitcount(key, start = 0, stop = -1)
368
+ node_for(key).bitcount(key, start, stop)
369
+ end
370
+
371
+ # Perform a bitwise operation between strings and store the resulting string in a key.
372
+ def bitop(operation, destkey, *keys)
373
+ ensure_same_node(:bitop, [destkey] + keys) do |node|
374
+ node.bitop(operation, destkey, *keys)
375
+ end
376
+ end
377
+
378
+ # Return the position of the first bit set to 1 or 0 in a string.
379
+ def bitpos(key, bit, start = nil, stop = nil)
380
+ node_for(key).bitpos(key, bit, start, stop)
381
+ end
382
+
295
383
  # Set the string value of a key and return its old value.
296
384
  def getset(key, value)
297
385
  node_for(key).getset(key, value)
@@ -306,7 +394,7 @@ class Redis
306
394
  get(key)
307
395
  end
308
396
 
309
- def []=(key,value)
397
+ def []=(key, value)
310
398
  set(key, value)
311
399
  end
312
400
 
@@ -315,6 +403,21 @@ class Redis
315
403
  node_for(key).llen(key)
316
404
  end
317
405
 
406
+ # Remove the first/last element in a list, append/prepend it to another list and return it.
407
+ def lmove(source, destination, where_source, where_destination)
408
+ ensure_same_node(:lmove, [source, destination]) do |node|
409
+ node.lmove(source, destination, where_source, where_destination)
410
+ end
411
+ end
412
+
413
+ # Remove the first/last element in a list and append/prepend it
414
+ # to another list and return it, or block until one is available.
415
+ def blmove(source, destination, where_source, where_destination, timeout: 0)
416
+ ensure_same_node(:lmove, [source, destination]) do |node|
417
+ node.blmove(source, destination, where_source, where_destination, timeout: timeout)
418
+ end
419
+ end
420
+
318
421
  # Prepend one or more values to a list.
319
422
  def lpush(key, value)
320
423
  node_for(key).lpush(key, value)
@@ -335,14 +438,14 @@ class Redis
335
438
  node_for(key).rpushx(key, value)
336
439
  end
337
440
 
338
- # Remove and get the first element in a list.
339
- def lpop(key)
340
- node_for(key).lpop(key)
441
+ # Remove and get the first elements in a list.
442
+ def lpop(key, count = nil)
443
+ node_for(key).lpop(key, count)
341
444
  end
342
445
 
343
- # Remove and get the last element in a list.
344
- def rpop(key)
345
- node_for(key).rpop(key)
446
+ # Remove and get the last elements in a list.
447
+ def rpop(key, count = nil)
448
+ node_for(key).rpop(key, count)
346
449
  end
347
450
 
348
451
  # Remove the last element in a list, append it to another list and return
@@ -354,14 +457,12 @@ class Redis
354
457
  end
355
458
 
356
459
  def _bpop(cmd, args)
357
- options = {}
358
-
359
- case args.last
360
- when Hash
460
+ timeout = if args.last.is_a?(Hash)
361
461
  options = args.pop
362
- when Integer
462
+ options[:timeout]
463
+ elsif args.last.respond_to?(:to_int)
363
464
  # Issue deprecation notice in obnoxious mode...
364
- options[:timeout] = args.pop
465
+ args.pop.to_int
365
466
  end
366
467
 
367
468
  if args.size > 1
@@ -371,7 +472,11 @@ class Redis
371
472
  keys = args.flatten
372
473
 
373
474
  ensure_same_node(cmd, keys) do |node|
374
- node.__send__(cmd, keys, options)
475
+ if timeout
476
+ node.__send__(cmd, keys, timeout: timeout)
477
+ else
478
+ node.__send__(cmd, keys)
479
+ end
375
480
  end
376
481
  end
377
482
 
@@ -389,15 +494,9 @@ class Redis
389
494
 
390
495
  # Pop a value from a list, push it to another list and return it; or block
391
496
  # until one is available.
392
- def brpoplpush(source, destination, options = {})
393
- case options
394
- when Integer
395
- # Issue deprecation notice in obnoxious mode...
396
- options = { :timeout => options }
397
- end
398
-
497
+ def brpoplpush(source, destination, deprecated_timeout = 0, **options)
399
498
  ensure_same_node(:brpoplpush, [source, destination]) do |node|
400
- node.brpoplpush(source, destination, options)
499
+ node.brpoplpush(source, destination, deprecated_timeout, **options)
401
500
  end
402
501
  end
403
502
 
@@ -447,13 +546,13 @@ class Redis
447
546
  end
448
547
 
449
548
  # Remove and return a random member from a set.
450
- def spop(key)
451
- node_for(key).spop(key)
549
+ def spop(key, count = nil)
550
+ node_for(key).spop(key, count)
452
551
  end
453
552
 
454
553
  # Get a random member from a set.
455
- def srandmember(key)
456
- node_for(key).srandmember(key)
554
+ def srandmember(key, count = nil)
555
+ node_for(key).srandmember(key, count)
457
556
  end
458
557
 
459
558
  # Move a member from one set to another.
@@ -468,11 +567,26 @@ class Redis
468
567
  node_for(key).sismember(key, member)
469
568
  end
470
569
 
570
+ # Determine if multiple values are members of a set.
571
+ def smismember(key, *members)
572
+ node_for(key).smismember(key, *members)
573
+ end
574
+
471
575
  # Get all the members in a set.
472
576
  def smembers(key)
473
577
  node_for(key).smembers(key)
474
578
  end
475
579
 
580
+ # Scan a set
581
+ def sscan(key, cursor, **options)
582
+ node_for(key).sscan(key, cursor, **options)
583
+ end
584
+
585
+ # Scan a set and return an enumerator
586
+ def sscan_each(key, **options, &block)
587
+ node_for(key).sscan_each(key, **options, &block)
588
+ end
589
+
476
590
  # Subtract multiple sets.
477
591
  def sdiff(*keys)
478
592
  ensure_same_node(:sdiff, keys) do |node|
@@ -525,6 +639,7 @@ class Redis
525
639
  def zadd(key, *args)
526
640
  node_for(key).zadd(key, *args)
527
641
  end
642
+ ruby2_keywords(:zadd) if respond_to?(:ruby2_keywords, true)
528
643
 
529
644
  # Increment the score of a member in a sorted set.
530
645
  def zincrby(key, increment, member)
@@ -541,15 +656,25 @@ class Redis
541
656
  node_for(key).zscore(key, member)
542
657
  end
543
658
 
659
+ # Get one or more random members from a sorted set.
660
+ def zrandmember(key, count = nil, **options)
661
+ node_for(key).zrandmember(key, count, **options)
662
+ end
663
+
664
+ # Get the scores associated with the given members in a sorted set.
665
+ def zmscore(key, *members)
666
+ node_for(key).zmscore(key, *members)
667
+ end
668
+
544
669
  # Return a range of members in a sorted set, by index.
545
- def zrange(key, start, stop, options = {})
546
- node_for(key).zrange(key, start, stop, options)
670
+ def zrange(key, start, stop, **options)
671
+ node_for(key).zrange(key, start, stop, **options)
547
672
  end
548
673
 
549
674
  # Return a range of members in a sorted set, by index, with scores ordered
550
675
  # from high to low.
551
- def zrevrange(key, start, stop, options = {})
552
- node_for(key).zrevrange(key, start, stop, options)
676
+ def zrevrange(key, start, stop, **options)
677
+ node_for(key).zrevrange(key, start, stop, **options)
553
678
  end
554
679
 
555
680
  # Determine the index of a member in a sorted set.
@@ -569,14 +694,14 @@ class Redis
569
694
  end
570
695
 
571
696
  # Return a range of members in a sorted set, by score.
572
- def zrangebyscore(key, min, max, options = {})
573
- node_for(key).zrangebyscore(key, min, max, options)
697
+ def zrangebyscore(key, min, max, **options)
698
+ node_for(key).zrangebyscore(key, min, max, **options)
574
699
  end
575
700
 
576
701
  # Return a range of members in a sorted set, by score, with scores ordered
577
702
  # from high to low.
578
- def zrevrangebyscore(key, max, min, options = {})
579
- node_for(key).zrevrangebyscore(key, max, min, options)
703
+ def zrevrangebyscore(key, max, min, **options)
704
+ node_for(key).zrevrangebyscore(key, max, min, **options)
580
705
  end
581
706
 
582
707
  # Remove all members in a sorted set within the given scores.
@@ -589,18 +714,25 @@ class Redis
589
714
  node_for(key).zcount(key, min, max)
590
715
  end
591
716
 
717
+ # Get the intersection of multiple sorted sets
718
+ def zinter(*keys, **options)
719
+ ensure_same_node(:zinter, keys) do |node|
720
+ node.zinter(*keys, **options)
721
+ end
722
+ end
723
+
592
724
  # Intersect multiple sorted sets and store the resulting sorted set in a new
593
725
  # key.
594
- def zinterstore(destination, keys, options = {})
726
+ def zinterstore(destination, keys, **options)
595
727
  ensure_same_node(:zinterstore, [destination] + keys) do |node|
596
- node.zinterstore(destination, keys, options)
728
+ node.zinterstore(destination, keys, **options)
597
729
  end
598
730
  end
599
731
 
600
732
  # Add multiple sorted sets and store the resulting sorted set in a new key.
601
- def zunionstore(destination, keys, options = {})
733
+ def zunionstore(destination, keys, **options)
602
734
  ensure_same_node(:zunionstore, [destination] + keys) do |node|
603
- node.zunionstore(destination, keys, options)
735
+ node.zunionstore(destination, keys, **options)
604
736
  end
605
737
  end
606
738
 
@@ -609,9 +741,9 @@ class Redis
609
741
  node_for(key).hlen(key)
610
742
  end
611
743
 
612
- # Set the string value of a hash field.
613
- def hset(key, field, value)
614
- node_for(key).hset(key, field, value)
744
+ # Set multiple hash fields to multiple values.
745
+ def hset(key, *attrs)
746
+ node_for(key).hset(key, *attrs)
615
747
  end
616
748
 
617
749
  # Set the value of a hash field, only if the field does not exist.
@@ -643,8 +775,8 @@ class Redis
643
775
  end
644
776
 
645
777
  # Delete one or more hash fields.
646
- def hdel(key, field)
647
- node_for(key).hdel(key, field)
778
+ def hdel(key, *fields)
779
+ node_for(key).hdel(key, *fields)
648
780
  end
649
781
 
650
782
  # Determine if a hash field exists.
@@ -683,7 +815,7 @@ class Redis
683
815
  end
684
816
 
685
817
  def subscribed?
686
- !! @subscribed_node
818
+ !!@subscribed_node
687
819
  end
688
820
 
689
821
  # Listen for messages published to the given channels.
@@ -701,7 +833,8 @@ class Redis
701
833
 
702
834
  # Stop listening for messages posted to the given channels.
703
835
  def unsubscribe(*channels)
704
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
836
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
837
+
705
838
  @subscribed_node.unsubscribe(*channels)
706
839
  end
707
840
 
@@ -717,13 +850,26 @@ class Redis
717
850
  end
718
851
 
719
852
  # Watch the given keys to determine execution of the MULTI/EXEC block.
720
- def watch(*keys)
721
- raise CannotDistribute, :watch
853
+ def watch(*keys, &block)
854
+ ensure_same_node(:watch, keys) do |node|
855
+ @watch_key = key_tag(keys.first) || keys.first.to_s
856
+
857
+ begin
858
+ node.watch(*keys, &block)
859
+ rescue StandardError
860
+ @watch_key = nil
861
+ raise
862
+ end
863
+ end
722
864
  end
723
865
 
724
866
  # Forget about all watched keys.
725
867
  def unwatch
726
- raise CannotDistribute, :unwatch
868
+ raise CannotDistribute, :unwatch unless @watch_key
869
+
870
+ result = node_for(@watch_key).unwatch
871
+ @watch_key = nil
872
+ result
727
873
  end
728
874
 
729
875
  def pipelined
@@ -731,18 +877,30 @@ class Redis
731
877
  end
732
878
 
733
879
  # Mark the start of a transaction block.
734
- def multi
735
- raise CannotDistribute, :multi
880
+ def multi(&block)
881
+ raise CannotDistribute, :multi unless @watch_key
882
+
883
+ result = node_for(@watch_key).multi(&block)
884
+ @watch_key = nil if block_given?
885
+ result
736
886
  end
737
887
 
738
888
  # Execute all commands issued after MULTI.
739
889
  def exec
740
- raise CannotDistribute, :exec
890
+ raise CannotDistribute, :exec unless @watch_key
891
+
892
+ result = node_for(@watch_key).exec
893
+ @watch_key = nil
894
+ result
741
895
  end
742
896
 
743
897
  # Discard all commands issued after MULTI.
744
898
  def discard
745
- raise CannotDistribute, :discard
899
+ raise CannotDistribute, :discard unless @watch_key
900
+
901
+ result = node_for(@watch_key).discard
902
+ @watch_key = nil
903
+ result
746
904
  end
747
905
 
748
906
  # Control remote script registry.
@@ -750,6 +908,26 @@ class Redis
750
908
  on_each_node(:script, subcommand, *args)
751
909
  end
752
910
 
911
+ # Add one or more members to a HyperLogLog structure.
912
+ def pfadd(key, member)
913
+ node_for(key).pfadd(key, member)
914
+ end
915
+
916
+ # Get the approximate cardinality of members added to HyperLogLog structure.
917
+ def pfcount(*keys)
918
+ ensure_same_node(:pfcount, keys.flatten(1)) do |node|
919
+ node.pfcount(keys)
920
+ end
921
+ end
922
+
923
+ # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
924
+ # the observed Sets of the source HyperLogLog structures.
925
+ def pfmerge(dest_key, *source_key)
926
+ ensure_same_node(:pfmerge, [dest_key, *source_key]) do |node|
927
+ node.pfmerge(dest_key, *source_key)
928
+ end
929
+ end
930
+
753
931
  def _eval(cmd, args)
754
932
  script = args.shift
755
933
  options = args.pop if args.last.is_a?(Hash)
@@ -777,7 +955,11 @@ class Redis
777
955
  "#<Redis client v#{Redis::VERSION} for #{nodes.map(&:id).join(', ')}>"
778
956
  end
779
957
 
780
- protected
958
+ def dup
959
+ self.class.new(@node_configs, @default_options)
960
+ end
961
+
962
+ protected
781
963
 
782
964
  def on_each_node(command, *args)
783
965
  nodes.map do |node|
data/lib/redis/errors.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  # Base error for all redis-rb errors.
3
5
  class BaseError < RuntimeError
@@ -37,4 +39,50 @@ class Redis
37
39
  # Raised when the connection was inherited by a child process.
38
40
  class InheritedError < BaseConnectionError
39
41
  end
42
+
43
+ # Raised when client options are invalid.
44
+ class InvalidClientOptionError < BaseError
45
+ end
46
+
47
+ class Cluster
48
+ # Raised when client connected to redis as cluster mode
49
+ # and some cluster subcommands were called.
50
+ class OrchestrationCommandNotSupported < BaseError
51
+ def initialize(command, subcommand = '')
52
+ str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
53
+ msg = "#{str} command should be used with care "\
54
+ 'only by applications orchestrating Redis Cluster, like redis-trib, '\
55
+ 'and the command if used out of the right context can leave the cluster '\
56
+ 'in a wrong state or cause data loss.'
57
+ super(msg)
58
+ end
59
+ end
60
+
61
+ # Raised when error occurs on any node of cluster.
62
+ class CommandErrorCollection < BaseError
63
+ attr_reader :errors
64
+
65
+ # @param errors [Hash{String => Redis::CommandError}]
66
+ # @param error_message [String]
67
+ def initialize(errors, error_message = 'Command errors were replied on any node')
68
+ @errors = errors
69
+ super(error_message)
70
+ end
71
+ end
72
+
73
+ # Raised when cluster client can't select node.
74
+ class AmbiguousNodeError < BaseError
75
+ def initialize(command)
76
+ super("Cluster client doesn't know which node the #{command} command should be sent to.")
77
+ end
78
+ end
79
+
80
+ # Raised when commands in pipelining include cross slot keys.
81
+ class CrossSlotPipeliningError < BaseError
82
+ def initialize(keys)
83
+ super("Cluster client couldn't send pipelining to single node. "\
84
+ "The commands include cross slot keys. #{keys}")
85
+ end
86
+ end
87
+ end
40
88
  end