redis 3.3.5 → 4.5.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 (130) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +161 -2
  3. data/README.md +144 -79
  4. data/lib/redis/client.rb +166 -90
  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 +6 -5
  17. data/lib/redis/connection/registry.rb +2 -1
  18. data/lib/redis/connection/ruby.rb +128 -129
  19. data/lib/redis/connection/synchrony.rb +21 -8
  20. data/lib/redis/connection.rb +4 -2
  21. data/lib/redis/distributed.rb +194 -72
  22. data/lib/redis/errors.rb +48 -0
  23. data/lib/redis/hash_ring.rb +30 -73
  24. data/lib/redis/pipeline.rb +55 -15
  25. data/lib/redis/subscribe.rb +11 -12
  26. data/lib/redis/version.rb +3 -1
  27. data/lib/redis.rb +1451 -403
  28. metadata +49 -202
  29. data/.gitignore +0 -16
  30. data/.travis/Gemfile +0 -11
  31. data/.travis.yml +0 -89
  32. data/.yardopts +0 -3
  33. data/Gemfile +0 -4
  34. data/Rakefile +0 -87
  35. data/benchmarking/logging.rb +0 -71
  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/consistency.rb +0 -114
  42. data/examples/dist_redis.rb +0 -43
  43. data/examples/incr-decr.rb +0 -17
  44. data/examples/list.rb +0 -26
  45. data/examples/pubsub.rb +0 -37
  46. data/examples/sentinel/sentinel.conf +0 -9
  47. data/examples/sentinel/start +0 -49
  48. data/examples/sentinel.rb +0 -41
  49. data/examples/sets.rb +0 -36
  50. data/examples/unicorn/config.ru +0 -3
  51. data/examples/unicorn/unicorn.rb +0 -20
  52. data/redis.gemspec +0 -44
  53. data/test/bitpos_test.rb +0 -69
  54. data/test/blocking_commands_test.rb +0 -42
  55. data/test/client_test.rb +0 -59
  56. data/test/command_map_test.rb +0 -30
  57. data/test/commands_on_hashes_test.rb +0 -21
  58. data/test/commands_on_hyper_log_log_test.rb +0 -21
  59. data/test/commands_on_lists_test.rb +0 -20
  60. data/test/commands_on_sets_test.rb +0 -77
  61. data/test/commands_on_sorted_sets_test.rb +0 -137
  62. data/test/commands_on_strings_test.rb +0 -101
  63. data/test/commands_on_value_types_test.rb +0 -133
  64. data/test/connection_handling_test.rb +0 -277
  65. data/test/connection_test.rb +0 -57
  66. data/test/db/.gitkeep +0 -0
  67. data/test/distributed_blocking_commands_test.rb +0 -46
  68. data/test/distributed_commands_on_hashes_test.rb +0 -10
  69. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  70. data/test/distributed_commands_on_lists_test.rb +0 -22
  71. data/test/distributed_commands_on_sets_test.rb +0 -83
  72. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  73. data/test/distributed_commands_on_strings_test.rb +0 -59
  74. data/test/distributed_commands_on_value_types_test.rb +0 -95
  75. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  76. data/test/distributed_connection_handling_test.rb +0 -23
  77. data/test/distributed_internals_test.rb +0 -79
  78. data/test/distributed_key_tags_test.rb +0 -52
  79. data/test/distributed_persistence_control_commands_test.rb +0 -26
  80. data/test/distributed_publish_subscribe_test.rb +0 -92
  81. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  82. data/test/distributed_scripting_test.rb +0 -102
  83. data/test/distributed_sorting_test.rb +0 -20
  84. data/test/distributed_test.rb +0 -58
  85. data/test/distributed_transactions_test.rb +0 -32
  86. data/test/encoding_test.rb +0 -18
  87. data/test/error_replies_test.rb +0 -59
  88. data/test/fork_safety_test.rb +0 -65
  89. data/test/helper.rb +0 -232
  90. data/test/helper_test.rb +0 -24
  91. data/test/internals_test.rb +0 -417
  92. data/test/lint/blocking_commands.rb +0 -150
  93. data/test/lint/hashes.rb +0 -162
  94. data/test/lint/hyper_log_log.rb +0 -60
  95. data/test/lint/lists.rb +0 -143
  96. data/test/lint/sets.rb +0 -140
  97. data/test/lint/sorted_sets.rb +0 -316
  98. data/test/lint/strings.rb +0 -260
  99. data/test/lint/value_types.rb +0 -122
  100. data/test/persistence_control_commands_test.rb +0 -26
  101. data/test/pipelining_commands_test.rb +0 -242
  102. data/test/publish_subscribe_test.rb +0 -282
  103. data/test/remote_server_control_commands_test.rb +0 -118
  104. data/test/scanning_test.rb +0 -413
  105. data/test/scripting_test.rb +0 -78
  106. data/test/sentinel_command_test.rb +0 -80
  107. data/test/sentinel_test.rb +0 -255
  108. data/test/sorting_test.rb +0 -59
  109. data/test/ssl_test.rb +0 -73
  110. data/test/support/connection/hiredis.rb +0 -1
  111. data/test/support/connection/ruby.rb +0 -1
  112. data/test/support/connection/synchrony.rb +0 -17
  113. data/test/support/redis_mock.rb +0 -130
  114. data/test/support/ssl/gen_certs.sh +0 -31
  115. data/test/support/ssl/trusted-ca.crt +0 -25
  116. data/test/support/ssl/trusted-ca.key +0 -27
  117. data/test/support/ssl/trusted-cert.crt +0 -81
  118. data/test/support/ssl/trusted-cert.key +0 -28
  119. data/test/support/ssl/untrusted-ca.crt +0 -26
  120. data/test/support/ssl/untrusted-ca.key +0 -27
  121. data/test/support/ssl/untrusted-cert.crt +0 -82
  122. data/test/support/ssl/untrusted-cert.key +0 -28
  123. data/test/support/wire/synchrony.rb +0 -24
  124. data/test/support/wire/thread.rb +0 -5
  125. data/test/synchrony_driver.rb +0 -88
  126. data/test/test.conf.erb +0 -9
  127. data/test/thread_safety_test.rb +0 -62
  128. data/test/transactions_test.rb +0 -264
  129. data/test/unknown_commands_test.rb +0 -14
  130. data/test/url_param_test.rb +0 -138
@@ -1,15 +1,17 @@
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
 
@@ -22,10 +24,14 @@ class Redis
22
24
  @default_options = options.dup
23
25
  node_configs.each { |node_config| add_node(node_config) }
24
26
  @subscribed_node = nil
27
+ @watch_key = nil
25
28
  end
26
29
 
27
30
  def node_for(key)
28
- @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)
29
35
  end
30
36
 
31
37
  def nodes
@@ -33,9 +39,9 @@ class Redis
33
39
  end
34
40
 
35
41
  def add_node(options)
36
- options = { :url => options } if options.is_a?(String)
42
+ options = { url: options } if options.is_a?(String)
37
43
  options = @default_options.merge(options)
38
- @ring.add_node Redis.new( options )
44
+ @ring.add_node Redis.new(options)
39
45
  end
40
46
 
41
47
  # Change the selected database for the current connection.
@@ -144,12 +150,12 @@ class Redis
144
150
  end
145
151
 
146
152
  # Create a key using the serialized value, previously obtained using DUMP.
147
- def restore(key, ttl, serialized_value)
148
- node_for(key).restore(key, ttl, serialized_value)
153
+ def restore(key, ttl, serialized_value, **options)
154
+ node_for(key).restore(key, ttl, serialized_value, **options)
149
155
  end
150
156
 
151
157
  # Transfer a key from the connected instance to another instance.
152
- def migrate(key, options)
158
+ def migrate(_key, _options)
153
159
  raise CannotDistribute, :migrate
154
160
  end
155
161
 
@@ -161,9 +167,42 @@ class Redis
161
167
  end
162
168
  end
163
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
+
164
178
  # Determine if a key exists.
165
- def exists(key)
166
- 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
167
206
  end
168
207
 
169
208
  # Find all keys matching the given pattern.
@@ -196,11 +235,11 @@ class Redis
196
235
  end
197
236
 
198
237
  # Sort the elements in a list, set or sorted set.
199
- def sort(key, options = {})
238
+ def sort(key, **options)
200
239
  keys = [key, options[:by], options[:store], *Array(options[:get])].compact
201
240
 
202
241
  ensure_same_node(:sort, keys) do |node|
203
- node.sort(key, options)
242
+ node.sort(key, **options)
204
243
  end
205
244
  end
206
245
 
@@ -235,8 +274,8 @@ class Redis
235
274
  end
236
275
 
237
276
  # Set the string value of a key.
238
- def set(key, value, options = {})
239
- node_for(key).set(key, value, options)
277
+ def set(key, value, **options)
278
+ node_for(key).set(key, value, **options)
240
279
  end
241
280
 
242
281
  # Set the time to live in seconds of a key.
@@ -255,20 +294,20 @@ class Redis
255
294
  end
256
295
 
257
296
  # Set multiple keys to multiple values.
258
- def mset(*args)
297
+ def mset(*_args)
259
298
  raise CannotDistribute, :mset
260
299
  end
261
300
 
262
- def mapped_mset(hash)
301
+ def mapped_mset(_hash)
263
302
  raise CannotDistribute, :mapped_mset
264
303
  end
265
304
 
266
305
  # Set multiple keys to multiple values, only if none of the keys exist.
267
- def msetnx(*args)
306
+ def msetnx(*_args)
268
307
  raise CannotDistribute, :msetnx
269
308
  end
270
309
 
271
- def mapped_msetnx(hash)
310
+ def mapped_msetnx(_hash)
272
311
  raise CannotDistribute, :mapped_msetnx
273
312
  end
274
313
 
@@ -277,13 +316,26 @@ class Redis
277
316
  node_for(key).get(key)
278
317
  end
279
318
 
280
- # 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.
281
330
  def mget(*keys)
282
- raise CannotDistribute, :mget
331
+ mapped_mget(*keys).values_at(*keys)
283
332
  end
284
333
 
334
+ # Get the values of all the given keys as a Hash.
285
335
  def mapped_mget(*keys)
286
- 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
287
339
  end
288
340
 
289
341
  # Overwrite part of a string at key starting at the specified offset.
@@ -324,7 +376,7 @@ class Redis
324
376
  end
325
377
 
326
378
  # Return the position of the first bit set to 1 or 0 in a string.
327
- def bitpos(key, bit, start=nil, stop=nil)
379
+ def bitpos(key, bit, start = nil, stop = nil)
328
380
  node_for(key).bitpos(key, bit, start, stop)
329
381
  end
330
382
 
@@ -342,7 +394,7 @@ class Redis
342
394
  get(key)
343
395
  end
344
396
 
345
- def []=(key,value)
397
+ def []=(key, value)
346
398
  set(key, value)
347
399
  end
348
400
 
@@ -351,6 +403,21 @@ class Redis
351
403
  node_for(key).llen(key)
352
404
  end
353
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
+
354
421
  # Prepend one or more values to a list.
355
422
  def lpush(key, value)
356
423
  node_for(key).lpush(key, value)
@@ -371,14 +438,14 @@ class Redis
371
438
  node_for(key).rpushx(key, value)
372
439
  end
373
440
 
374
- # Remove and get the first element in a list.
375
- def lpop(key)
376
- 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)
377
444
  end
378
445
 
379
- # Remove and get the last element in a list.
380
- def rpop(key)
381
- 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)
382
449
  end
383
450
 
384
451
  # Remove the last element in a list, append it to another list and return
@@ -390,14 +457,12 @@ class Redis
390
457
  end
391
458
 
392
459
  def _bpop(cmd, args)
393
- options = {}
394
-
395
- case args.last
396
- when Hash
460
+ timeout = if args.last.is_a?(Hash)
397
461
  options = args.pop
398
- when Integer
462
+ options[:timeout]
463
+ elsif args.last.respond_to?(:to_int)
399
464
  # Issue deprecation notice in obnoxious mode...
400
- options[:timeout] = args.pop
465
+ args.pop.to_int
401
466
  end
402
467
 
403
468
  if args.size > 1
@@ -407,7 +472,11 @@ class Redis
407
472
  keys = args.flatten
408
473
 
409
474
  ensure_same_node(cmd, keys) do |node|
410
- node.__send__(cmd, keys, options)
475
+ if timeout
476
+ node.__send__(cmd, keys, timeout: timeout)
477
+ else
478
+ node.__send__(cmd, keys)
479
+ end
411
480
  end
412
481
  end
413
482
 
@@ -425,15 +494,9 @@ class Redis
425
494
 
426
495
  # Pop a value from a list, push it to another list and return it; or block
427
496
  # until one is available.
428
- def brpoplpush(source, destination, options = {})
429
- case options
430
- when Integer
431
- # Issue deprecation notice in obnoxious mode...
432
- options = { :timeout => options }
433
- end
434
-
497
+ def brpoplpush(source, destination, deprecated_timeout = 0, **options)
435
498
  ensure_same_node(:brpoplpush, [source, destination]) do |node|
436
- node.brpoplpush(source, destination, options)
499
+ node.brpoplpush(source, destination, deprecated_timeout, **options)
437
500
  end
438
501
  end
439
502
 
@@ -504,11 +567,26 @@ class Redis
504
567
  node_for(key).sismember(key, member)
505
568
  end
506
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
+
507
575
  # Get all the members in a set.
508
576
  def smembers(key)
509
577
  node_for(key).smembers(key)
510
578
  end
511
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
+
512
590
  # Subtract multiple sets.
513
591
  def sdiff(*keys)
514
592
  ensure_same_node(:sdiff, keys) do |node|
@@ -561,6 +639,7 @@ class Redis
561
639
  def zadd(key, *args)
562
640
  node_for(key).zadd(key, *args)
563
641
  end
642
+ ruby2_keywords(:zadd) if respond_to?(:ruby2_keywords, true)
564
643
 
565
644
  # Increment the score of a member in a sorted set.
566
645
  def zincrby(key, increment, member)
@@ -577,15 +656,25 @@ class Redis
577
656
  node_for(key).zscore(key, member)
578
657
  end
579
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
+
580
669
  # Return a range of members in a sorted set, by index.
581
- def zrange(key, start, stop, options = {})
582
- node_for(key).zrange(key, start, stop, options)
670
+ def zrange(key, start, stop, **options)
671
+ node_for(key).zrange(key, start, stop, **options)
583
672
  end
584
673
 
585
674
  # Return a range of members in a sorted set, by index, with scores ordered
586
675
  # from high to low.
587
- def zrevrange(key, start, stop, options = {})
588
- node_for(key).zrevrange(key, start, stop, options)
676
+ def zrevrange(key, start, stop, **options)
677
+ node_for(key).zrevrange(key, start, stop, **options)
589
678
  end
590
679
 
591
680
  # Determine the index of a member in a sorted set.
@@ -605,14 +694,14 @@ class Redis
605
694
  end
606
695
 
607
696
  # Return a range of members in a sorted set, by score.
608
- def zrangebyscore(key, min, max, options = {})
609
- node_for(key).zrangebyscore(key, min, max, options)
697
+ def zrangebyscore(key, min, max, **options)
698
+ node_for(key).zrangebyscore(key, min, max, **options)
610
699
  end
611
700
 
612
701
  # Return a range of members in a sorted set, by score, with scores ordered
613
702
  # from high to low.
614
- def zrevrangebyscore(key, max, min, options = {})
615
- node_for(key).zrevrangebyscore(key, max, min, options)
703
+ def zrevrangebyscore(key, max, min, **options)
704
+ node_for(key).zrevrangebyscore(key, max, min, **options)
616
705
  end
617
706
 
618
707
  # Remove all members in a sorted set within the given scores.
@@ -625,18 +714,25 @@ class Redis
625
714
  node_for(key).zcount(key, min, max)
626
715
  end
627
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
+
628
724
  # Intersect multiple sorted sets and store the resulting sorted set in a new
629
725
  # key.
630
- def zinterstore(destination, keys, options = {})
726
+ def zinterstore(destination, keys, **options)
631
727
  ensure_same_node(:zinterstore, [destination] + keys) do |node|
632
- node.zinterstore(destination, keys, options)
728
+ node.zinterstore(destination, keys, **options)
633
729
  end
634
730
  end
635
731
 
636
732
  # Add multiple sorted sets and store the resulting sorted set in a new key.
637
- def zunionstore(destination, keys, options = {})
733
+ def zunionstore(destination, keys, **options)
638
734
  ensure_same_node(:zunionstore, [destination] + keys) do |node|
639
- node.zunionstore(destination, keys, options)
735
+ node.zunionstore(destination, keys, **options)
640
736
  end
641
737
  end
642
738
 
@@ -645,9 +741,9 @@ class Redis
645
741
  node_for(key).hlen(key)
646
742
  end
647
743
 
648
- # Set the string value of a hash field.
649
- def hset(key, field, value)
650
- 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)
651
747
  end
652
748
 
653
749
  # Set the value of a hash field, only if the field does not exist.
@@ -679,8 +775,8 @@ class Redis
679
775
  end
680
776
 
681
777
  # Delete one or more hash fields.
682
- def hdel(key, field)
683
- node_for(key).hdel(key, field)
778
+ def hdel(key, *fields)
779
+ node_for(key).hdel(key, *fields)
684
780
  end
685
781
 
686
782
  # Determine if a hash field exists.
@@ -719,7 +815,7 @@ class Redis
719
815
  end
720
816
 
721
817
  def subscribed?
722
- !! @subscribed_node
818
+ !!@subscribed_node
723
819
  end
724
820
 
725
821
  # Listen for messages published to the given channels.
@@ -737,7 +833,8 @@ class Redis
737
833
 
738
834
  # Stop listening for messages posted to the given channels.
739
835
  def unsubscribe(*channels)
740
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
836
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
837
+
741
838
  @subscribed_node.unsubscribe(*channels)
742
839
  end
743
840
 
@@ -753,13 +850,26 @@ class Redis
753
850
  end
754
851
 
755
852
  # Watch the given keys to determine execution of the MULTI/EXEC block.
756
- def watch(*keys)
757
- 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
758
864
  end
759
865
 
760
866
  # Forget about all watched keys.
761
867
  def unwatch
762
- raise CannotDistribute, :unwatch
868
+ raise CannotDistribute, :unwatch unless @watch_key
869
+
870
+ result = node_for(@watch_key).unwatch
871
+ @watch_key = nil
872
+ result
763
873
  end
764
874
 
765
875
  def pipelined
@@ -767,18 +877,30 @@ class Redis
767
877
  end
768
878
 
769
879
  # Mark the start of a transaction block.
770
- def multi
771
- 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
772
886
  end
773
887
 
774
888
  # Execute all commands issued after MULTI.
775
889
  def exec
776
- raise CannotDistribute, :exec
890
+ raise CannotDistribute, :exec unless @watch_key
891
+
892
+ result = node_for(@watch_key).exec
893
+ @watch_key = nil
894
+ result
777
895
  end
778
896
 
779
897
  # Discard all commands issued after MULTI.
780
898
  def discard
781
- raise CannotDistribute, :discard
899
+ raise CannotDistribute, :discard unless @watch_key
900
+
901
+ result = node_for(@watch_key).discard
902
+ @watch_key = nil
903
+ result
782
904
  end
783
905
 
784
906
  # Control remote script registry.
@@ -837,7 +959,7 @@ class Redis
837
959
  self.class.new(@node_configs, @default_options)
838
960
  end
839
961
 
840
- protected
962
+ protected
841
963
 
842
964
  def on_each_node(command, *args)
843
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