redis 3.0.0 → 4.5.0

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