redis 3.3.5 → 4.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +225 -2
  3. data/README.md +169 -89
  4. data/lib/redis/client.rb +177 -100
  5. data/lib/redis/cluster/command.rb +79 -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 +120 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +34 -0
  11. data/lib/redis/cluster/option.rb +100 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +46 -0
  14. data/lib/redis/cluster.rb +315 -0
  15. data/lib/redis/commands/bitmaps.rb +63 -0
  16. data/lib/redis/commands/cluster.rb +45 -0
  17. data/lib/redis/commands/connection.rb +58 -0
  18. data/lib/redis/commands/geo.rb +84 -0
  19. data/lib/redis/commands/hashes.rb +251 -0
  20. data/lib/redis/commands/hyper_log_log.rb +37 -0
  21. data/lib/redis/commands/keys.rb +411 -0
  22. data/lib/redis/commands/lists.rb +289 -0
  23. data/lib/redis/commands/pubsub.rb +72 -0
  24. data/lib/redis/commands/scripting.rb +114 -0
  25. data/lib/redis/commands/server.rb +188 -0
  26. data/lib/redis/commands/sets.rb +207 -0
  27. data/lib/redis/commands/sorted_sets.rb +812 -0
  28. data/lib/redis/commands/streams.rb +382 -0
  29. data/lib/redis/commands/strings.rb +313 -0
  30. data/lib/redis/commands/transactions.rb +139 -0
  31. data/lib/redis/commands.rb +242 -0
  32. data/lib/redis/connection/command_helper.rb +7 -10
  33. data/lib/redis/connection/hiredis.rb +5 -5
  34. data/lib/redis/connection/registry.rb +2 -1
  35. data/lib/redis/connection/ruby.rb +136 -128
  36. data/lib/redis/connection/synchrony.rb +24 -9
  37. data/lib/redis/connection.rb +3 -1
  38. data/lib/redis/distributed.rb +231 -72
  39. data/lib/redis/errors.rb +57 -0
  40. data/lib/redis/hash_ring.rb +30 -73
  41. data/lib/redis/pipeline.rb +178 -13
  42. data/lib/redis/subscribe.rb +11 -12
  43. data/lib/redis/version.rb +3 -1
  44. data/lib/redis.rb +173 -2661
  45. metadata +66 -202
  46. data/.gitignore +0 -16
  47. data/.travis/Gemfile +0 -11
  48. data/.travis.yml +0 -89
  49. data/.yardopts +0 -3
  50. data/Gemfile +0 -4
  51. data/Rakefile +0 -87
  52. data/benchmarking/logging.rb +0 -71
  53. data/benchmarking/pipeline.rb +0 -51
  54. data/benchmarking/speed.rb +0 -21
  55. data/benchmarking/suite.rb +0 -24
  56. data/benchmarking/worker.rb +0 -71
  57. data/examples/basic.rb +0 -15
  58. data/examples/consistency.rb +0 -114
  59. data/examples/dist_redis.rb +0 -43
  60. data/examples/incr-decr.rb +0 -17
  61. data/examples/list.rb +0 -26
  62. data/examples/pubsub.rb +0 -37
  63. data/examples/sentinel/sentinel.conf +0 -9
  64. data/examples/sentinel/start +0 -49
  65. data/examples/sentinel.rb +0 -41
  66. data/examples/sets.rb +0 -36
  67. data/examples/unicorn/config.ru +0 -3
  68. data/examples/unicorn/unicorn.rb +0 -20
  69. data/redis.gemspec +0 -44
  70. data/test/bitpos_test.rb +0 -69
  71. data/test/blocking_commands_test.rb +0 -42
  72. data/test/client_test.rb +0 -59
  73. data/test/command_map_test.rb +0 -30
  74. data/test/commands_on_hashes_test.rb +0 -21
  75. data/test/commands_on_hyper_log_log_test.rb +0 -21
  76. data/test/commands_on_lists_test.rb +0 -20
  77. data/test/commands_on_sets_test.rb +0 -77
  78. data/test/commands_on_sorted_sets_test.rb +0 -137
  79. data/test/commands_on_strings_test.rb +0 -101
  80. data/test/commands_on_value_types_test.rb +0 -133
  81. data/test/connection_handling_test.rb +0 -277
  82. data/test/connection_test.rb +0 -57
  83. data/test/db/.gitkeep +0 -0
  84. data/test/distributed_blocking_commands_test.rb +0 -46
  85. data/test/distributed_commands_on_hashes_test.rb +0 -10
  86. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  87. data/test/distributed_commands_on_lists_test.rb +0 -22
  88. data/test/distributed_commands_on_sets_test.rb +0 -83
  89. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  90. data/test/distributed_commands_on_strings_test.rb +0 -59
  91. data/test/distributed_commands_on_value_types_test.rb +0 -95
  92. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  93. data/test/distributed_connection_handling_test.rb +0 -23
  94. data/test/distributed_internals_test.rb +0 -79
  95. data/test/distributed_key_tags_test.rb +0 -52
  96. data/test/distributed_persistence_control_commands_test.rb +0 -26
  97. data/test/distributed_publish_subscribe_test.rb +0 -92
  98. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  99. data/test/distributed_scripting_test.rb +0 -102
  100. data/test/distributed_sorting_test.rb +0 -20
  101. data/test/distributed_test.rb +0 -58
  102. data/test/distributed_transactions_test.rb +0 -32
  103. data/test/encoding_test.rb +0 -18
  104. data/test/error_replies_test.rb +0 -59
  105. data/test/fork_safety_test.rb +0 -65
  106. data/test/helper.rb +0 -232
  107. data/test/helper_test.rb +0 -24
  108. data/test/internals_test.rb +0 -417
  109. data/test/lint/blocking_commands.rb +0 -150
  110. data/test/lint/hashes.rb +0 -162
  111. data/test/lint/hyper_log_log.rb +0 -60
  112. data/test/lint/lists.rb +0 -143
  113. data/test/lint/sets.rb +0 -140
  114. data/test/lint/sorted_sets.rb +0 -316
  115. data/test/lint/strings.rb +0 -260
  116. data/test/lint/value_types.rb +0 -122
  117. data/test/persistence_control_commands_test.rb +0 -26
  118. data/test/pipelining_commands_test.rb +0 -242
  119. data/test/publish_subscribe_test.rb +0 -282
  120. data/test/remote_server_control_commands_test.rb +0 -118
  121. data/test/scanning_test.rb +0 -413
  122. data/test/scripting_test.rb +0 -78
  123. data/test/sentinel_command_test.rb +0 -80
  124. data/test/sentinel_test.rb +0 -255
  125. data/test/sorting_test.rb +0 -59
  126. data/test/ssl_test.rb +0 -73
  127. data/test/support/connection/hiredis.rb +0 -1
  128. data/test/support/connection/ruby.rb +0 -1
  129. data/test/support/connection/synchrony.rb +0 -17
  130. data/test/support/redis_mock.rb +0 -130
  131. data/test/support/ssl/gen_certs.sh +0 -31
  132. data/test/support/ssl/trusted-ca.crt +0 -25
  133. data/test/support/ssl/trusted-ca.key +0 -27
  134. data/test/support/ssl/trusted-cert.crt +0 -81
  135. data/test/support/ssl/trusted-cert.key +0 -28
  136. data/test/support/ssl/untrusted-ca.crt +0 -26
  137. data/test/support/ssl/untrusted-ca.key +0 -27
  138. data/test/support/ssl/untrusted-cert.crt +0 -82
  139. data/test/support/ssl/untrusted-cert.key +0 -28
  140. data/test/support/wire/synchrony.rb +0 -24
  141. data/test/support/wire/thread.rb +0 -5
  142. data/test/synchrony_driver.rb +0 -88
  143. data/test/test.conf.erb +0 -9
  144. data/test/thread_safety_test.rb +0 -62
  145. data/test/transactions_test.rb +0 -264
  146. data/test/unknown_commands_test.rb +0 -14
  147. data/test/url_param_test.rb +0 -138
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "redis/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,38 @@ 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
+ ::Redis.deprecate!(
182
+ "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
183
+ "use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
184
+ "(#{::Kernel.caller(1, 1).first})\n"
185
+ )
186
+ exists?(*args)
187
+ else
188
+ keys_per_node = args.group_by { |key| node_for(key) }
189
+ keys_per_node.inject(0) do |sum, (node, keys)|
190
+ sum + node._exists(*keys)
191
+ end
192
+ end
193
+ end
194
+
195
+ # Determine if any of the keys exists.
196
+ def exists?(*args)
197
+ keys_per_node = args.group_by { |key| node_for(key) }
198
+ keys_per_node.each do |node, keys|
199
+ return true if node.exists?(*keys)
200
+ end
201
+ false
167
202
  end
168
203
 
169
204
  # Find all keys matching the given pattern.
@@ -176,6 +211,13 @@ class Redis
176
211
  node_for(key).move(key, db)
177
212
  end
178
213
 
214
+ # Copy a value from one key to another.
215
+ def copy(source, destination, **options)
216
+ ensure_same_node(:copy, [source, destination]) do |node|
217
+ node.copy(source, destination, **options)
218
+ end
219
+ end
220
+
179
221
  # Return a random key from the keyspace.
180
222
  def randomkey
181
223
  raise CannotDistribute, :randomkey
@@ -196,11 +238,11 @@ class Redis
196
238
  end
197
239
 
198
240
  # Sort the elements in a list, set or sorted set.
199
- def sort(key, options = {})
241
+ def sort(key, **options)
200
242
  keys = [key, options[:by], options[:store], *Array(options[:get])].compact
201
243
 
202
244
  ensure_same_node(:sort, keys) do |node|
203
- node.sort(key, options)
245
+ node.sort(key, **options)
204
246
  end
205
247
  end
206
248
 
@@ -235,8 +277,8 @@ class Redis
235
277
  end
236
278
 
237
279
  # Set the string value of a key.
238
- def set(key, value, options = {})
239
- node_for(key).set(key, value, options)
280
+ def set(key, value, **options)
281
+ node_for(key).set(key, value, **options)
240
282
  end
241
283
 
242
284
  # Set the time to live in seconds of a key.
@@ -255,20 +297,20 @@ class Redis
255
297
  end
256
298
 
257
299
  # Set multiple keys to multiple values.
258
- def mset(*args)
300
+ def mset(*_args)
259
301
  raise CannotDistribute, :mset
260
302
  end
261
303
 
262
- def mapped_mset(hash)
304
+ def mapped_mset(_hash)
263
305
  raise CannotDistribute, :mapped_mset
264
306
  end
265
307
 
266
308
  # Set multiple keys to multiple values, only if none of the keys exist.
267
- def msetnx(*args)
309
+ def msetnx(*_args)
268
310
  raise CannotDistribute, :msetnx
269
311
  end
270
312
 
271
- def mapped_msetnx(hash)
313
+ def mapped_msetnx(_hash)
272
314
  raise CannotDistribute, :mapped_msetnx
273
315
  end
274
316
 
@@ -277,13 +319,26 @@ class Redis
277
319
  node_for(key).get(key)
278
320
  end
279
321
 
280
- # Get the values of all the given keys.
322
+ # Get the value of a key and delete it.
323
+ def getdel(key)
324
+ node_for(key).getdel(key)
325
+ end
326
+
327
+ # Get the value of a key and sets its time to live based on options.
328
+ def getex(key, **options)
329
+ node_for(key).getex(key, **options)
330
+ end
331
+
332
+ # Get the values of all the given keys as an Array.
281
333
  def mget(*keys)
282
- raise CannotDistribute, :mget
334
+ mapped_mget(*keys).values_at(*keys)
283
335
  end
284
336
 
337
+ # Get the values of all the given keys as a Hash.
285
338
  def mapped_mget(*keys)
286
- raise CannotDistribute, :mapped_mget
339
+ keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
340
+ results.merge! node.mapped_mget(*subkeys)
341
+ end
287
342
  end
288
343
 
289
344
  # Overwrite part of a string at key starting at the specified offset.
@@ -324,7 +379,7 @@ class Redis
324
379
  end
325
380
 
326
381
  # Return the position of the first bit set to 1 or 0 in a string.
327
- def bitpos(key, bit, start=nil, stop=nil)
382
+ def bitpos(key, bit, start = nil, stop = nil)
328
383
  node_for(key).bitpos(key, bit, start, stop)
329
384
  end
330
385
 
@@ -342,7 +397,7 @@ class Redis
342
397
  get(key)
343
398
  end
344
399
 
345
- def []=(key,value)
400
+ def []=(key, value)
346
401
  set(key, value)
347
402
  end
348
403
 
@@ -351,6 +406,21 @@ class Redis
351
406
  node_for(key).llen(key)
352
407
  end
353
408
 
409
+ # Remove the first/last element in a list, append/prepend it to another list and return it.
410
+ def lmove(source, destination, where_source, where_destination)
411
+ ensure_same_node(:lmove, [source, destination]) do |node|
412
+ node.lmove(source, destination, where_source, where_destination)
413
+ end
414
+ end
415
+
416
+ # Remove the first/last element in a list and append/prepend it
417
+ # to another list and return it, or block until one is available.
418
+ def blmove(source, destination, where_source, where_destination, timeout: 0)
419
+ ensure_same_node(:lmove, [source, destination]) do |node|
420
+ node.blmove(source, destination, where_source, where_destination, timeout: timeout)
421
+ end
422
+ end
423
+
354
424
  # Prepend one or more values to a list.
355
425
  def lpush(key, value)
356
426
  node_for(key).lpush(key, value)
@@ -371,14 +441,14 @@ class Redis
371
441
  node_for(key).rpushx(key, value)
372
442
  end
373
443
 
374
- # Remove and get the first element in a list.
375
- def lpop(key)
376
- node_for(key).lpop(key)
444
+ # Remove and get the first elements in a list.
445
+ def lpop(key, count = nil)
446
+ node_for(key).lpop(key, count)
377
447
  end
378
448
 
379
- # Remove and get the last element in a list.
380
- def rpop(key)
381
- node_for(key).rpop(key)
449
+ # Remove and get the last elements in a list.
450
+ def rpop(key, count = nil)
451
+ node_for(key).rpop(key, count)
382
452
  end
383
453
 
384
454
  # Remove the last element in a list, append it to another list and return
@@ -390,14 +460,12 @@ class Redis
390
460
  end
391
461
 
392
462
  def _bpop(cmd, args)
393
- options = {}
394
-
395
- case args.last
396
- when Hash
463
+ timeout = if args.last.is_a?(Hash)
397
464
  options = args.pop
398
- when Integer
465
+ options[:timeout]
466
+ elsif args.last.respond_to?(:to_int)
399
467
  # Issue deprecation notice in obnoxious mode...
400
- options[:timeout] = args.pop
468
+ args.pop.to_int
401
469
  end
402
470
 
403
471
  if args.size > 1
@@ -407,7 +475,11 @@ class Redis
407
475
  keys = args.flatten
408
476
 
409
477
  ensure_same_node(cmd, keys) do |node|
410
- node.__send__(cmd, keys, options)
478
+ if timeout
479
+ node.__send__(cmd, keys, timeout: timeout)
480
+ else
481
+ node.__send__(cmd, keys)
482
+ end
411
483
  end
412
484
  end
413
485
 
@@ -425,15 +497,9 @@ class Redis
425
497
 
426
498
  # Pop a value from a list, push it to another list and return it; or block
427
499
  # 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
-
500
+ def brpoplpush(source, destination, deprecated_timeout = 0, **options)
435
501
  ensure_same_node(:brpoplpush, [source, destination]) do |node|
436
- node.brpoplpush(source, destination, options)
502
+ node.brpoplpush(source, destination, deprecated_timeout, **options)
437
503
  end
438
504
  end
439
505
 
@@ -504,11 +570,26 @@ class Redis
504
570
  node_for(key).sismember(key, member)
505
571
  end
506
572
 
573
+ # Determine if multiple values are members of a set.
574
+ def smismember(key, *members)
575
+ node_for(key).smismember(key, *members)
576
+ end
577
+
507
578
  # Get all the members in a set.
508
579
  def smembers(key)
509
580
  node_for(key).smembers(key)
510
581
  end
511
582
 
583
+ # Scan a set
584
+ def sscan(key, cursor, **options)
585
+ node_for(key).sscan(key, cursor, **options)
586
+ end
587
+
588
+ # Scan a set and return an enumerator
589
+ def sscan_each(key, **options, &block)
590
+ node_for(key).sscan_each(key, **options, &block)
591
+ end
592
+
512
593
  # Subtract multiple sets.
513
594
  def sdiff(*keys)
514
595
  ensure_same_node(:sdiff, keys) do |node|
@@ -561,6 +642,7 @@ class Redis
561
642
  def zadd(key, *args)
562
643
  node_for(key).zadd(key, *args)
563
644
  end
645
+ ruby2_keywords(:zadd) if respond_to?(:ruby2_keywords, true)
564
646
 
565
647
  # Increment the score of a member in a sorted set.
566
648
  def zincrby(key, increment, member)
@@ -577,15 +659,33 @@ class Redis
577
659
  node_for(key).zscore(key, member)
578
660
  end
579
661
 
580
- # 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)
662
+ # Get one or more random members from a sorted set.
663
+ def zrandmember(key, count = nil, **options)
664
+ node_for(key).zrandmember(key, count, **options)
665
+ end
666
+
667
+ # Get the scores associated with the given members in a sorted set.
668
+ def zmscore(key, *members)
669
+ node_for(key).zmscore(key, *members)
670
+ end
671
+
672
+ # Return a range of members in a sorted set, by index, score or lexicographical ordering.
673
+ def zrange(key, start, stop, **options)
674
+ node_for(key).zrange(key, start, stop, **options)
675
+ end
676
+
677
+ # Select a range of members in a sorted set, by index, score or lexicographical ordering
678
+ # and store the resulting sorted set in a new key.
679
+ def zrangestore(dest_key, src_key, start, stop, **options)
680
+ ensure_same_node(:zrangestore, [dest_key, src_key]) do |node|
681
+ node.zrangestore(dest_key, src_key, start, stop, **options)
682
+ end
583
683
  end
584
684
 
585
685
  # Return a range of members in a sorted set, by index, with scores ordered
586
686
  # from high to low.
587
- def zrevrange(key, start, stop, options = {})
588
- node_for(key).zrevrange(key, start, stop, options)
687
+ def zrevrange(key, start, stop, **options)
688
+ node_for(key).zrevrange(key, start, stop, **options)
589
689
  end
590
690
 
591
691
  # Determine the index of a member in a sorted set.
@@ -605,14 +705,14 @@ class Redis
605
705
  end
606
706
 
607
707
  # 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)
708
+ def zrangebyscore(key, min, max, **options)
709
+ node_for(key).zrangebyscore(key, min, max, **options)
610
710
  end
611
711
 
612
712
  # Return a range of members in a sorted set, by score, with scores ordered
613
713
  # from high to low.
614
- def zrevrangebyscore(key, max, min, options = {})
615
- node_for(key).zrevrangebyscore(key, max, min, options)
714
+ def zrevrangebyscore(key, max, min, **options)
715
+ node_for(key).zrevrangebyscore(key, max, min, **options)
616
716
  end
617
717
 
618
718
  # Remove all members in a sorted set within the given scores.
@@ -625,18 +725,47 @@ class Redis
625
725
  node_for(key).zcount(key, min, max)
626
726
  end
627
727
 
728
+ # Get the intersection of multiple sorted sets
729
+ def zinter(*keys, **options)
730
+ ensure_same_node(:zinter, keys) do |node|
731
+ node.zinter(*keys, **options)
732
+ end
733
+ end
734
+
628
735
  # Intersect multiple sorted sets and store the resulting sorted set in a new
629
736
  # key.
630
- def zinterstore(destination, keys, options = {})
737
+ def zinterstore(destination, keys, **options)
631
738
  ensure_same_node(:zinterstore, [destination] + keys) do |node|
632
- node.zinterstore(destination, keys, options)
739
+ node.zinterstore(destination, keys, **options)
740
+ end
741
+ end
742
+
743
+ # Return the union of multiple sorted sets.
744
+ def zunion(*keys, **options)
745
+ ensure_same_node(:zunion, keys) do |node|
746
+ node.zunion(*keys, **options)
633
747
  end
634
748
  end
635
749
 
636
750
  # Add multiple sorted sets and store the resulting sorted set in a new key.
637
- def zunionstore(destination, keys, options = {})
751
+ def zunionstore(destination, keys, **options)
638
752
  ensure_same_node(:zunionstore, [destination] + keys) do |node|
639
- node.zunionstore(destination, keys, options)
753
+ node.zunionstore(destination, keys, **options)
754
+ end
755
+ end
756
+
757
+ # Return the difference between the first and all successive input sorted sets.
758
+ def zdiff(*keys, **options)
759
+ ensure_same_node(:zdiff, keys) do |node|
760
+ node.zdiff(*keys, **options)
761
+ end
762
+ end
763
+
764
+ # Compute the difference between the first and all successive input sorted sets
765
+ # and store the resulting sorted set in a new key.
766
+ def zdiffstore(destination, keys, **options)
767
+ ensure_same_node(:zdiffstore, [destination] + keys) do |node|
768
+ node.zdiffstore(destination, keys, **options)
640
769
  end
641
770
  end
642
771
 
@@ -645,9 +774,9 @@ class Redis
645
774
  node_for(key).hlen(key)
646
775
  end
647
776
 
648
- # Set the string value of a hash field.
649
- def hset(key, field, value)
650
- node_for(key).hset(key, field, value)
777
+ # Set multiple hash fields to multiple values.
778
+ def hset(key, *attrs)
779
+ node_for(key).hset(key, *attrs)
651
780
  end
652
781
 
653
782
  # Set the value of a hash field, only if the field does not exist.
@@ -678,9 +807,13 @@ class Redis
678
807
  Hash[*fields.zip(hmget(key, *fields)).flatten]
679
808
  end
680
809
 
810
+ def hrandfield(key, count = nil, **options)
811
+ node_for(key).hrandfield(key, count, **options)
812
+ end
813
+
681
814
  # Delete one or more hash fields.
682
- def hdel(key, field)
683
- node_for(key).hdel(key, field)
815
+ def hdel(key, *fields)
816
+ node_for(key).hdel(key, *fields)
684
817
  end
685
818
 
686
819
  # Determine if a hash field exists.
@@ -719,7 +852,7 @@ class Redis
719
852
  end
720
853
 
721
854
  def subscribed?
722
- !! @subscribed_node
855
+ !!@subscribed_node
723
856
  end
724
857
 
725
858
  # Listen for messages published to the given channels.
@@ -737,7 +870,8 @@ class Redis
737
870
 
738
871
  # Stop listening for messages posted to the given channels.
739
872
  def unsubscribe(*channels)
740
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
873
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
874
+
741
875
  @subscribed_node.unsubscribe(*channels)
742
876
  end
743
877
 
@@ -753,13 +887,26 @@ class Redis
753
887
  end
754
888
 
755
889
  # Watch the given keys to determine execution of the MULTI/EXEC block.
756
- def watch(*keys)
757
- raise CannotDistribute, :watch
890
+ def watch(*keys, &block)
891
+ ensure_same_node(:watch, keys) do |node|
892
+ @watch_key = key_tag(keys.first) || keys.first.to_s
893
+
894
+ begin
895
+ node.watch(*keys, &block)
896
+ rescue StandardError
897
+ @watch_key = nil
898
+ raise
899
+ end
900
+ end
758
901
  end
759
902
 
760
903
  # Forget about all watched keys.
761
904
  def unwatch
762
- raise CannotDistribute, :unwatch
905
+ raise CannotDistribute, :unwatch unless @watch_key
906
+
907
+ result = node_for(@watch_key).unwatch
908
+ @watch_key = nil
909
+ result
763
910
  end
764
911
 
765
912
  def pipelined
@@ -767,18 +914,30 @@ class Redis
767
914
  end
768
915
 
769
916
  # Mark the start of a transaction block.
770
- def multi
771
- raise CannotDistribute, :multi
917
+ def multi(&block)
918
+ raise CannotDistribute, :multi unless @watch_key
919
+
920
+ result = node_for(@watch_key).multi(&block)
921
+ @watch_key = nil if block_given?
922
+ result
772
923
  end
773
924
 
774
925
  # Execute all commands issued after MULTI.
775
926
  def exec
776
- raise CannotDistribute, :exec
927
+ raise CannotDistribute, :exec unless @watch_key
928
+
929
+ result = node_for(@watch_key).exec
930
+ @watch_key = nil
931
+ result
777
932
  end
778
933
 
779
934
  # Discard all commands issued after MULTI.
780
935
  def discard
781
- raise CannotDistribute, :discard
936
+ raise CannotDistribute, :discard unless @watch_key
937
+
938
+ result = node_for(@watch_key).discard
939
+ @watch_key = nil
940
+ result
782
941
  end
783
942
 
784
943
  # Control remote script registry.
@@ -837,7 +996,7 @@ class Redis
837
996
  self.class.new(@node_configs, @default_options)
838
997
  end
839
998
 
840
- protected
999
+ protected
841
1000
 
842
1001
  def on_each_node(command, *args)
843
1002
  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,59 @@ 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 failed to fetch cluster state information by commands.
50
+ class InitialSetupError < BaseError
51
+ # @param errors [Array<Redis::BaseError>]
52
+ def initialize(errors)
53
+ super("Redis client could not fetch cluster information: #{errors.map(&:message).uniq.join(',')}")
54
+ end
55
+ end
56
+
57
+ # Raised when client connected to redis as cluster mode
58
+ # and some cluster subcommands were called.
59
+ class OrchestrationCommandNotSupported < BaseError
60
+ def initialize(command, subcommand = '')
61
+ str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
62
+ msg = "#{str} command should be used with care "\
63
+ 'only by applications orchestrating Redis Cluster, like redis-trib, '\
64
+ 'and the command if used out of the right context can leave the cluster '\
65
+ 'in a wrong state or cause data loss.'
66
+ super(msg)
67
+ end
68
+ end
69
+
70
+ # Raised when error occurs on any node of cluster.
71
+ class CommandErrorCollection < BaseError
72
+ attr_reader :errors
73
+
74
+ # @param errors [Hash{String => Redis::CommandError}]
75
+ # @param error_message [String]
76
+ def initialize(errors, error_message = 'Command errors were replied on any node')
77
+ @errors = errors
78
+ super(error_message)
79
+ end
80
+ end
81
+
82
+ # Raised when cluster client can't select node.
83
+ class AmbiguousNodeError < BaseError
84
+ def initialize(command)
85
+ super("Cluster client doesn't know which node the #{command} command should be sent to.")
86
+ end
87
+ end
88
+
89
+ # Raised when commands in pipelining include cross slot keys.
90
+ class CrossSlotPipeliningError < BaseError
91
+ def initialize(keys)
92
+ super("Cluster client couldn't send pipelining to single node. "\
93
+ "The commands include cross slot keys. #{keys}")
94
+ end
95
+ end
96
+ end
40
97
  end