redis 4.8.1 → 5.0.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -4
  3. data/README.md +75 -161
  4. data/lib/redis/client.rb +78 -606
  5. data/lib/redis/commands/bitmaps.rb +4 -1
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +8 -5
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +1 -19
  12. data/lib/redis/commands/lists.rb +20 -25
  13. data/lib/redis/commands/pubsub.rb +7 -25
  14. data/lib/redis/commands/server.rb +14 -14
  15. data/lib/redis/commands/sets.rb +31 -40
  16. data/lib/redis/commands/sorted_sets.rb +18 -12
  17. data/lib/redis/commands/streams.rb +12 -10
  18. data/lib/redis/commands/strings.rb +14 -13
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +1 -6
  21. data/lib/redis/distributed.rb +86 -64
  22. data/lib/redis/errors.rb +11 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +43 -222
  25. data/lib/redis/subscribe.rb +23 -15
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +80 -185
  28. metadata +11 -55
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -68
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
@@ -20,7 +20,7 @@ class Redis
20
20
  def initialize(node_configs, options = {})
21
21
  @tag = options[:tag] || /^\{(.+?)\}/
22
22
  @ring = options[:ring] || HashRing.new
23
- @node_configs = node_configs.dup
23
+ @node_configs = node_configs.map(&:dup)
24
24
  @default_options = options.dup
25
25
  node_configs.each { |node_config| add_node(node_config) }
26
26
  @subscribed_node = nil
@@ -41,6 +41,8 @@ class Redis
41
41
  def add_node(options)
42
42
  options = { url: options } if options.is_a?(String)
43
43
  options = @default_options.merge(options)
44
+ options.delete(:tag)
45
+ options.delete(:ring)
44
46
  @ring.add_node Redis.new(options)
45
47
  end
46
48
 
@@ -64,6 +66,10 @@ class Redis
64
66
  on_each_node :quit
65
67
  end
66
68
 
69
+ def close
70
+ on_each_node :close
71
+ end
72
+
67
73
  # Asynchronously save the dataset to disk.
68
74
  def bgsave
69
75
  on_each_node :bgsave
@@ -161,6 +167,7 @@ class Redis
161
167
 
162
168
  # Delete a key.
163
169
  def del(*args)
170
+ args.flatten!(1)
164
171
  keys_per_node = args.group_by { |key| node_for(key) }
165
172
  keys_per_node.inject(0) do |sum, (node, keys)|
166
173
  sum + node.del(*keys)
@@ -169,6 +176,7 @@ class Redis
169
176
 
170
177
  # Unlink keys.
171
178
  def unlink(*args)
179
+ args.flatten!(1)
172
180
  keys_per_node = args.group_by { |key| node_for(key) }
173
181
  keys_per_node.inject(0) do |sum, (node, keys)|
174
182
  sum + node.unlink(*keys)
@@ -177,23 +185,16 @@ class Redis
177
185
 
178
186
  # Determine if a key exists.
179
187
  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
188
+ args.flatten!(1)
189
+ keys_per_node = args.group_by { |key| node_for(key) }
190
+ keys_per_node.inject(0) do |sum, (node, keys)|
191
+ sum + node.exists(*keys)
192
192
  end
193
193
  end
194
194
 
195
195
  # Determine if any of the keys exists.
196
196
  def exists?(*args)
197
+ args.flatten!(1)
197
198
  keys_per_node = args.group_by { |key| node_for(key) }
198
199
  keys_per_node.each do |node, keys|
199
200
  return true if node.exists?(*keys)
@@ -297,7 +298,7 @@ class Redis
297
298
  end
298
299
 
299
300
  # Set multiple keys to multiple values.
300
- def mset(*_args)
301
+ def mset(*)
301
302
  raise CannotDistribute, :mset
302
303
  end
303
304
 
@@ -306,7 +307,7 @@ class Redis
306
307
  end
307
308
 
308
309
  # Set multiple keys to multiple values, only if none of the keys exist.
309
- def msetnx(*_args)
310
+ def msetnx(*)
310
311
  raise CannotDistribute, :msetnx
311
312
  end
312
313
 
@@ -331,11 +332,13 @@ class Redis
331
332
 
332
333
  # Get the values of all the given keys as an Array.
333
334
  def mget(*keys)
335
+ keys.flatten!(1)
334
336
  mapped_mget(*keys).values_at(*keys)
335
337
  end
336
338
 
337
339
  # Get the values of all the given keys as a Hash.
338
340
  def mapped_mget(*keys)
341
+ keys.flatten!(1)
339
342
  keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
340
343
  results.merge! node.mapped_mget(*subkeys)
341
344
  end
@@ -373,8 +376,9 @@ class Redis
373
376
 
374
377
  # Perform a bitwise operation between strings and store the resulting string in a key.
375
378
  def bitop(operation, destkey, *keys)
379
+ keys.flatten!(1)
376
380
  ensure_same_node(:bitop, [destkey] + keys) do |node|
377
- node.bitop(operation, destkey, *keys)
381
+ node.bitop(operation, destkey, keys)
378
382
  end
379
383
  end
380
384
 
@@ -463,23 +467,15 @@ class Redis
463
467
  timeout = if args.last.is_a?(Hash)
464
468
  options = args.pop
465
469
  options[:timeout]
466
- elsif args.last.respond_to?(:to_int)
467
- last_arg = args.pop
468
- ::Redis.deprecate!(
469
- "Passing the timeout as a positional argument is deprecated, it should be passed as a keyword argument:\n" \
470
- " redis.#{cmd}(#{args.map(&:inspect).join(', ')}, timeout: #{last_arg.to_int})" \
471
- "(called from: #{caller(2, 1).first})"
472
- )
473
- last_arg.to_int
474
470
  end
475
471
 
476
- keys = args.flatten
472
+ args.flatten!(1)
477
473
 
478
- ensure_same_node(cmd, keys) do |node|
474
+ ensure_same_node(cmd, args) do |node|
479
475
  if timeout
480
- node.__send__(cmd, keys, timeout: timeout)
476
+ node.__send__(cmd, args, timeout: timeout)
481
477
  else
482
- node.__send__(cmd, keys)
478
+ node.__send__(cmd, args)
483
479
  end
484
480
  end
485
481
  end
@@ -490,6 +486,18 @@ class Redis
490
486
  _bpop(:blpop, args)
491
487
  end
492
488
 
489
+ def bzpopmax(*args)
490
+ _bpop(:bzpopmax, args) do |reply|
491
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
492
+ end
493
+ end
494
+
495
+ def bzpopmin(*args)
496
+ _bpop(:bzpopmin, args) do |reply|
497
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
498
+ end
499
+ end
500
+
493
501
  # Remove and get the last element in a list, or block until one is
494
502
  # available.
495
503
  def brpop(*args)
@@ -498,9 +506,9 @@ class Redis
498
506
 
499
507
  # Pop a value from a list, push it to another list and return it; or block
500
508
  # until one is available.
501
- def brpoplpush(source, destination, deprecated_timeout = 0, **options)
509
+ def brpoplpush(source, destination, **options)
502
510
  ensure_same_node(:brpoplpush, [source, destination]) do |node|
503
- node.brpoplpush(source, destination, deprecated_timeout, **options)
511
+ node.brpoplpush(source, destination, **options)
504
512
  end
505
513
  end
506
514
 
@@ -540,23 +548,23 @@ class Redis
540
548
  end
541
549
 
542
550
  # Add one or more members to a set.
543
- def sadd(key, member)
544
- node_for(key).sadd(key, member)
551
+ def sadd(key, *members)
552
+ node_for(key).sadd(key, *members)
545
553
  end
546
554
 
547
555
  # Add one or more members to a set.
548
- def sadd?(key, member)
549
- node_for(key).sadd?(key, member)
556
+ def sadd?(key, *members)
557
+ node_for(key).sadd?(key, *members)
550
558
  end
551
559
 
552
560
  # Remove one or more members from a set.
553
- def srem(key, member)
554
- node_for(key).srem(key, member)
561
+ def srem(key, *members)
562
+ node_for(key).srem(key, *members)
555
563
  end
556
564
 
557
565
  # Remove one or more members from a set.
558
- def srem?(key, member)
559
- node_for(key).srem?(key, member)
566
+ def srem?(key, *members)
567
+ node_for(key).srem?(key, *members)
560
568
  end
561
569
 
562
570
  # Remove and return a random member from a set.
@@ -603,43 +611,49 @@ class Redis
603
611
 
604
612
  # Subtract multiple sets.
605
613
  def sdiff(*keys)
614
+ keys.flatten!(1)
606
615
  ensure_same_node(:sdiff, keys) do |node|
607
- node.sdiff(*keys)
616
+ node.sdiff(keys)
608
617
  end
609
618
  end
610
619
 
611
620
  # Subtract multiple sets and store the resulting set in a key.
612
621
  def sdiffstore(destination, *keys)
613
- ensure_same_node(:sdiffstore, [destination] + keys) do |node|
614
- node.sdiffstore(destination, *keys)
622
+ keys.flatten!(1)
623
+ ensure_same_node(:sdiffstore, [destination].concat(keys)) do |node|
624
+ node.sdiffstore(destination, keys)
615
625
  end
616
626
  end
617
627
 
618
628
  # Intersect multiple sets.
619
629
  def sinter(*keys)
630
+ keys.flatten!(1)
620
631
  ensure_same_node(:sinter, keys) do |node|
621
- node.sinter(*keys)
632
+ node.sinter(keys)
622
633
  end
623
634
  end
624
635
 
625
636
  # Intersect multiple sets and store the resulting set in a key.
626
637
  def sinterstore(destination, *keys)
627
- ensure_same_node(:sinterstore, [destination] + keys) do |node|
628
- node.sinterstore(destination, *keys)
638
+ keys.flatten!(1)
639
+ ensure_same_node(:sinterstore, [destination].concat(keys)) do |node|
640
+ node.sinterstore(destination, keys)
629
641
  end
630
642
  end
631
643
 
632
644
  # Add multiple sets.
633
645
  def sunion(*keys)
646
+ keys.flatten!(1)
634
647
  ensure_same_node(:sunion, keys) do |node|
635
- node.sunion(*keys)
648
+ node.sunion(keys)
636
649
  end
637
650
  end
638
651
 
639
652
  # Add multiple sets and store the resulting set in a key.
640
653
  def sunionstore(destination, *keys)
641
- ensure_same_node(:sunionstore, [destination] + keys) do |node|
642
- node.sunionstore(destination, *keys)
654
+ keys.flatten!(1)
655
+ ensure_same_node(:sunionstore, [destination].concat(keys)) do |node|
656
+ node.sunionstore(destination, keys)
643
657
  end
644
658
  end
645
659
 
@@ -738,43 +752,49 @@ class Redis
738
752
 
739
753
  # Get the intersection of multiple sorted sets
740
754
  def zinter(*keys, **options)
755
+ keys.flatten!(1)
741
756
  ensure_same_node(:zinter, keys) do |node|
742
- node.zinter(*keys, **options)
757
+ node.zinter(keys, **options)
743
758
  end
744
759
  end
745
760
 
746
761
  # Intersect multiple sorted sets and store the resulting sorted set in a new
747
762
  # key.
748
- def zinterstore(destination, keys, **options)
749
- ensure_same_node(:zinterstore, [destination] + keys) do |node|
763
+ def zinterstore(destination, *keys, **options)
764
+ keys.flatten!(1)
765
+ ensure_same_node(:zinterstore, [destination].concat(keys)) do |node|
750
766
  node.zinterstore(destination, keys, **options)
751
767
  end
752
768
  end
753
769
 
754
770
  # Return the union of multiple sorted sets.
755
771
  def zunion(*keys, **options)
772
+ keys.flatten!(1)
756
773
  ensure_same_node(:zunion, keys) do |node|
757
- node.zunion(*keys, **options)
774
+ node.zunion(keys, **options)
758
775
  end
759
776
  end
760
777
 
761
778
  # Add multiple sorted sets and store the resulting sorted set in a new key.
762
- def zunionstore(destination, keys, **options)
763
- ensure_same_node(:zunionstore, [destination] + keys) do |node|
779
+ def zunionstore(destination, *keys, **options)
780
+ keys.flatten!(1)
781
+ ensure_same_node(:zunionstore, [destination].concat(keys)) do |node|
764
782
  node.zunionstore(destination, keys, **options)
765
783
  end
766
784
  end
767
785
 
768
786
  # Return the difference between the first and all successive input sorted sets.
769
787
  def zdiff(*keys, **options)
788
+ keys.flatten!(1)
770
789
  ensure_same_node(:zdiff, keys) do |node|
771
- node.zdiff(*keys, **options)
790
+ node.zdiff(keys, **options)
772
791
  end
773
792
  end
774
793
 
775
794
  # Compute the difference between the first and all successive input sorted sets
776
795
  # and store the resulting sorted set in a new key.
777
- def zdiffstore(destination, keys, **options)
796
+ def zdiffstore(destination, *keys, **options)
797
+ keys.flatten!(1)
778
798
  ensure_same_node(:zdiffstore, [destination] + keys) do |node|
779
799
  node.zdiffstore(destination, keys, **options)
780
800
  end
@@ -801,7 +821,7 @@ class Redis
801
821
  end
802
822
 
803
823
  def mapped_hmset(key, hash)
804
- node_for(key).hmset(key, *hash.to_a.flatten)
824
+ node_for(key).hmset(key, hash)
805
825
  end
806
826
 
807
827
  # Get the value of a hash field.
@@ -811,11 +831,13 @@ class Redis
811
831
 
812
832
  # Get the values of all the given hash fields.
813
833
  def hmget(key, *fields)
814
- node_for(key).hmget(key, *fields)
834
+ fields.flatten!(1)
835
+ node_for(key).hmget(key, fields)
815
836
  end
816
837
 
817
838
  def mapped_hmget(key, *fields)
818
- Hash[*fields.zip(hmget(key, *fields)).flatten]
839
+ fields.flatten!(1)
840
+ node_for(key).mapped_hmget(key, fields)
819
841
  end
820
842
 
821
843
  def hrandfield(key, count = nil, **options)
@@ -824,7 +846,8 @@ class Redis
824
846
 
825
847
  # Delete one or more hash fields.
826
848
  def hdel(key, *fields)
827
- node_for(key).hdel(key, *fields)
849
+ fields.flatten!(1)
850
+ node_for(key).hdel(key, fields)
828
851
  end
829
852
 
830
853
  # Determine if a hash field exists.
@@ -881,7 +904,7 @@ class Redis
881
904
 
882
905
  # Stop listening for messages posted to the given channels.
883
906
  def unsubscribe(*channels)
884
- raise "Can't unsubscribe if not subscribed." unless subscribed?
907
+ raise SubscriptionError, "Can't unsubscribe if not subscribed." unless subscribed?
885
908
 
886
909
  @subscribed_node.unsubscribe(*channels)
887
910
  end
@@ -928,9 +951,7 @@ class Redis
928
951
  def multi(&block)
929
952
  raise CannotDistribute, :multi unless @watch_key
930
953
 
931
- result = node_for(@watch_key).multi(&block)
932
- @watch_key = nil if block_given?
933
- result
954
+ node_for(@watch_key).multi(&block)
934
955
  end
935
956
 
936
957
  # Execute all commands issued after MULTI.
@@ -1020,7 +1041,8 @@ class Redis
1020
1041
  end
1021
1042
 
1022
1043
  def key_tag(key)
1023
- key.to_s[@tag, 1] if @tag
1044
+ key = key.to_s
1045
+ key[@tag, 1] if key.match?(@tag)
1024
1046
  end
1025
1047
 
1026
1048
  def ensure_same_node(command, keys)
data/lib/redis/errors.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Redis
4
4
  # Base error for all redis-rb errors.
5
- class BaseError < RuntimeError
5
+ class BaseError < StandardError
6
6
  end
7
7
 
8
8
  # Raised by the connection when a protocol error occurs.
@@ -20,6 +20,15 @@ class Redis
20
20
  class CommandError < BaseError
21
21
  end
22
22
 
23
+ class PermissionError < CommandError
24
+ end
25
+
26
+ class WrongTypeError < CommandError
27
+ end
28
+
29
+ class ReadOnlyError < CommandError
30
+ end
31
+
23
32
  # Base error for connection related errors.
24
33
  class BaseConnectionError < BaseError
25
34
  end
@@ -44,54 +53,6 @@ class Redis
44
53
  class InvalidClientOptionError < BaseError
45
54
  end
46
55
 
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
56
+ class SubscriptionError < BaseError
96
57
  end
97
58
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'zlib'
4
+ require 'digest/md5'
4
5
 
5
6
  class Redis
6
7
  class HashRing
@@ -25,7 +26,7 @@ class Redis
25
26
  def add_node(node)
26
27
  @nodes << node
27
28
  @replicas.times do |i|
28
- key = Zlib.crc32("#{node.id}:#{i}")
29
+ key = server_hash_for("#{node.id}:#{i}")
29
30
  @ring[key] = node
30
31
  @sorted_keys << key
31
32
  end
@@ -35,7 +36,7 @@ class Redis
35
36
  def remove_node(node)
36
37
  @nodes.reject! { |n| n.id == node.id }
37
38
  @replicas.times do |i|
38
- key = Zlib.crc32("#{node.id}:#{i}")
39
+ key = server_hash_for("#{node.id}:#{i}")
39
40
  @ring.delete(key)
40
41
  @sorted_keys.reject! { |k| k == key }
41
42
  end
@@ -43,47 +44,46 @@ class Redis
43
44
 
44
45
  # get the node in the hash ring for this key
45
46
  def get_node(key)
46
- get_node_pos(key)[0]
47
- end
48
-
49
- def get_node_pos(key)
50
- return [nil, nil] if @ring.empty?
51
-
52
- crc = Zlib.crc32(key)
53
- idx = HashRing.binary_search(@sorted_keys, crc)
54
- [@ring[@sorted_keys[idx]], idx]
47
+ hash = hash_for(key)
48
+ idx = binary_search(@sorted_keys, hash)
49
+ @ring[@sorted_keys[idx]]
55
50
  end
56
51
 
57
52
  def iter_nodes(key)
58
53
  return [nil, nil] if @ring.empty?
59
54
 
60
- _, pos = get_node_pos(key)
55
+ crc = hash_for(key)
56
+ pos = binary_search(@sorted_keys, crc)
61
57
  @ring.size.times do |n|
62
58
  yield @ring[@sorted_keys[(pos + n) % @ring.size]]
63
59
  end
64
60
  end
65
61
 
62
+ private
63
+
64
+ def hash_for(key)
65
+ Zlib.crc32(key)
66
+ end
67
+
68
+ def server_hash_for(key)
69
+ Digest::MD5.digest(key).unpack1("L>")
70
+ end
71
+
66
72
  # Find the closest index in HashRing with value <= the given value
67
- def self.binary_search(ary, value)
68
- upper = ary.size - 1
73
+ def binary_search(ary, value)
74
+ upper = ary.size
69
75
  lower = 0
70
- idx = 0
71
-
72
- while lower <= upper
73
- idx = (lower + upper) / 2
74
- comp = ary[idx] <=> value
75
76
 
76
- if comp == 0
77
- return idx
78
- elsif comp > 0
79
- upper = idx - 1
77
+ while lower < upper
78
+ mid = (lower + upper) / 2
79
+ if ary[mid] > value
80
+ upper = mid
80
81
  else
81
- lower = idx + 1
82
+ lower = mid + 1
82
83
  end
83
84
  end
84
85
 
85
- upper = ary.size - 1 if upper < 0
86
- upper
86
+ upper - 1
87
87
  end
88
88
  end
89
89
  end