redis 4.2.5 → 5.0.7

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +167 -0
  3. data/README.md +102 -162
  4. data/lib/redis/client.rb +84 -589
  5. data/lib/redis/commands/bitmaps.rb +66 -0
  6. data/lib/redis/commands/cluster.rb +28 -0
  7. data/lib/redis/commands/connection.rb +53 -0
  8. data/lib/redis/commands/geo.rb +84 -0
  9. data/lib/redis/commands/hashes.rb +254 -0
  10. data/lib/redis/commands/hyper_log_log.rb +37 -0
  11. data/lib/redis/commands/keys.rb +437 -0
  12. data/lib/redis/commands/lists.rb +339 -0
  13. data/lib/redis/commands/pubsub.rb +54 -0
  14. data/lib/redis/commands/scripting.rb +114 -0
  15. data/lib/redis/commands/server.rb +188 -0
  16. data/lib/redis/commands/sets.rb +214 -0
  17. data/lib/redis/commands/sorted_sets.rb +884 -0
  18. data/lib/redis/commands/streams.rb +402 -0
  19. data/lib/redis/commands/strings.rb +314 -0
  20. data/lib/redis/commands/transactions.rb +115 -0
  21. data/lib/redis/commands.rb +237 -0
  22. data/lib/redis/distributed.rb +220 -75
  23. data/lib/redis/errors.rb +15 -41
  24. data/lib/redis/hash_ring.rb +26 -26
  25. data/lib/redis/pipeline.rb +66 -120
  26. data/lib/redis/subscribe.rb +23 -15
  27. data/lib/redis/version.rb +1 -1
  28. data/lib/redis.rb +110 -3441
  29. metadata +27 -54
  30. data/lib/redis/cluster/command.rb +0 -81
  31. data/lib/redis/cluster/command_loader.rb +0 -34
  32. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  33. data/lib/redis/cluster/node.rb +0 -107
  34. data/lib/redis/cluster/node_key.rb +0 -31
  35. data/lib/redis/cluster/node_loader.rb +0 -37
  36. data/lib/redis/cluster/option.rb +0 -90
  37. data/lib/redis/cluster/slot.rb +0 -86
  38. data/lib/redis/cluster/slot_loader.rb +0 -49
  39. data/lib/redis/cluster.rb +0 -295
  40. data/lib/redis/connection/command_helper.rb +0 -39
  41. data/lib/redis/connection/hiredis.rb +0 -67
  42. data/lib/redis/connection/registry.rb +0 -13
  43. data/lib/redis/connection/ruby.rb +0 -427
  44. data/lib/redis/connection/synchrony.rb +0 -146
  45. data/lib/redis/connection.rb +0 -11
@@ -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
@@ -1,126 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "delegate"
4
+
3
5
  class Redis
4
- class Pipeline
6
+ class PipelinedConnection
5
7
  attr_accessor :db
6
- attr_reader :client
7
-
8
- attr :futures
9
8
 
10
- def initialize(client)
11
- @client = client.is_a?(Pipeline) ? client.client : client
12
- @with_reconnect = true
13
- @shutdown = false
14
- @futures = []
9
+ def initialize(pipeline, futures = [])
10
+ @pipeline = pipeline
11
+ @futures = futures
15
12
  end
16
13
 
17
- def timeout
18
- client.timeout
19
- end
14
+ include Commands
20
15
 
21
- def with_reconnect?
22
- @with_reconnect
16
+ def pipelined
17
+ yield self
23
18
  end
24
19
 
25
- def without_reconnect?
26
- !@with_reconnect
20
+ def multi
21
+ transaction = MultiConnection.new(@pipeline, @futures)
22
+ send_command([:multi])
23
+ size = @futures.size
24
+ yield transaction
25
+ multi_future = MultiFuture.new(@futures[size..-1])
26
+ @pipeline.call_v([:exec]) do |result|
27
+ multi_future._set(result)
28
+ end
29
+ @futures << multi_future
30
+ multi_future
27
31
  end
28
32
 
29
- def shutdown?
30
- @shutdown
31
- end
33
+ private
32
34
 
33
- def empty?
34
- @futures.empty?
35
+ def synchronize
36
+ yield self
35
37
  end
36
38
 
37
- def call(command, timeout: nil, &block)
38
- # A pipeline that contains a shutdown should not raise ECONNRESET when
39
- # the connection is gone.
40
- @shutdown = true if command.first == :shutdown
41
- future = Future.new(command, block, timeout)
39
+ def send_command(command, &block)
40
+ future = Future.new(command, block)
41
+ @pipeline.call_v(command) do |result|
42
+ future._set(result)
43
+ end
42
44
  @futures << future
43
45
  future
44
46
  end
45
47
 
46
- def call_with_timeout(command, timeout, &block)
47
- call(command, timeout: timeout, &block)
48
- end
49
-
50
- def call_pipeline(pipeline)
51
- @shutdown = true if pipeline.shutdown?
52
- @futures.concat(pipeline.futures)
53
- @db = pipeline.db
54
- nil
55
- end
56
-
57
- def commands
58
- @futures.map(&:_command)
59
- end
60
-
61
- def timeouts
62
- @futures.map(&:timeout)
63
- end
64
-
65
- def with_reconnect(val = true)
66
- @with_reconnect = false unless val
67
- yield
68
- end
69
-
70
- def without_reconnect(&blk)
71
- with_reconnect(false, &blk)
72
- end
73
-
74
- def finish(replies, &blk)
75
- if blk
76
- futures.each_with_index.map do |future, i|
77
- future._set(blk.call(replies[i]))
78
- end
79
- else
80
- futures.each_with_index.map do |future, i|
81
- future._set(replies[i])
82
- end
48
+ def send_blocking_command(command, timeout, &block)
49
+ future = Future.new(command, block)
50
+ @pipeline.blocking_call_v(timeout, command) do |result|
51
+ future._set(result)
83
52
  end
53
+ @futures << future
54
+ future
84
55
  end
56
+ end
85
57
 
86
- class Multi < self
87
- def finish(replies)
88
- exec = replies.last
89
-
90
- return if exec.nil? # The transaction failed because of WATCH.
91
-
92
- # EXEC command failed.
93
- raise exec if exec.is_a?(CommandError)
94
-
95
- if exec.size < futures.size
96
- # Some command wasn't recognized by Redis.
97
- command_error = replies.detect { |r| r.is_a?(CommandError) }
98
- raise command_error
99
- end
58
+ class MultiConnection < PipelinedConnection
59
+ def multi
60
+ raise Redis::Error, "Can't nest multi transaction"
61
+ end
100
62
 
101
- super(exec) do |reply|
102
- # Because an EXEC returns nested replies, hiredis won't be able to
103
- # convert an error reply to a CommandError instance itself. This is
104
- # specific to MULTI/EXEC, so we solve this here.
105
- reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply
106
- end
107
- end
63
+ private
108
64
 
109
- def timeouts
110
- if empty?
111
- []
112
- else
113
- [nil, *super, nil]
114
- end
115
- end
116
-
117
- def commands
118
- if empty?
119
- []
120
- else
121
- [[:multi]] + super + [[:exec]]
122
- end
123
- end
65
+ # Blocking commands inside transaction behave like non-blocking.
66
+ # It shouldn't be done though.
67
+ # https://redis.io/commands/blpop/#blpop-inside-a-multi--exec-transaction
68
+ def send_blocking_command(command, _timeout, &block)
69
+ send_command(command, &block)
124
70
  end
125
71
  end
126
72
 
@@ -133,23 +79,10 @@ class Redis
133
79
  class Future < BasicObject
134
80
  FutureNotReady = ::Redis::FutureNotReady.new
135
81
 
136
- attr_reader :timeout
137
-
138
- def initialize(command, transformation, timeout)
82
+ def initialize(command, coerce)
139
83
  @command = command
140
- @transformation = transformation
141
- @timeout = timeout
142
84
  @object = FutureNotReady
143
- end
144
-
145
- def ==(_other)
146
- message = +"The methods == and != are deprecated for Redis::Future and will be removed in 4.2.0"
147
- message << " - You probably meant to call .value == or .value !="
148
- message << " (#{::Kernel.caller(1, 1).first})\n"
149
-
150
- ::Kernel.warn(message)
151
-
152
- super
85
+ @coerce = coerce
153
86
  end
154
87
 
155
88
  def inspect
@@ -157,16 +90,12 @@ class Redis
157
90
  end
158
91
 
159
92
  def _set(object)
160
- @object = @transformation ? @transformation.call(object) : object
93
+ @object = @coerce ? @coerce.call(object) : object
161
94
  value
162
95
  end
163
96
 
164
- def _command
165
- @command
166
- end
167
-
168
97
  def value
169
- ::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
98
+ ::Kernel.raise(@object) if @object.is_a?(::StandardError)
170
99
  @object
171
100
  end
172
101
 
@@ -178,4 +107,21 @@ class Redis
178
107
  Future
179
108
  end
180
109
  end
110
+
111
+ class MultiFuture < Future
112
+ def initialize(futures)
113
+ @futures = futures
114
+ @command = [:exec]
115
+ @object = FutureNotReady
116
+ end
117
+
118
+ def _set(replies)
119
+ if replies
120
+ @futures.each_with_index do |future, index|
121
+ future._set(replies[index])
122
+ end
123
+ end
124
+ @object = replies
125
+ end
126
+ end
181
127
  end
@@ -4,10 +4,13 @@ class Redis
4
4
  class SubscribedClient
5
5
  def initialize(client)
6
6
  @client = client
7
+ @write_monitor = Monitor.new
7
8
  end
8
9
 
9
- def call(command)
10
- @client.process([command])
10
+ def call_v(command)
11
+ @write_monitor.synchronize do
12
+ @client.call_v(command)
13
+ end
11
14
  end
12
15
 
13
16
  def subscribe(*channels, &block)
@@ -27,11 +30,15 @@ class Redis
27
30
  end
28
31
 
29
32
  def unsubscribe(*channels)
30
- call([:unsubscribe, *channels])
33
+ call_v([:unsubscribe, *channels])
31
34
  end
32
35
 
33
36
  def punsubscribe(*channels)
34
- call([:punsubscribe, *channels])
37
+ call_v([:punsubscribe, *channels])
38
+ end
39
+
40
+ def close
41
+ @client.close
35
42
  end
36
43
 
37
44
  protected
@@ -39,13 +46,17 @@ class Redis
39
46
  def subscription(start, stop, channels, block, timeout = 0)
40
47
  sub = Subscription.new(&block)
41
48
 
42
- unsubscribed = false
43
-
44
- @client.call_loop([start, *channels], timeout) do |line|
45
- type, *rest = line
46
- sub.callbacks[type].call(*rest)
47
- unsubscribed = type == stop && rest.last == 0
48
- break if unsubscribed
49
+ call_v([start, *channels])
50
+ while event = @client.next_event(timeout)
51
+ if event.is_a?(::RedisClient::CommandError)
52
+ raise Client::ERROR_MAPPING.fetch(event.class), event.message
53
+ end
54
+
55
+ type, *rest = event
56
+ if callback = sub.callbacks[type]
57
+ callback.call(*rest)
58
+ end
59
+ break if type == stop && rest.last == 0
49
60
  end
50
61
  # No need to unsubscribe here. The real client closes the connection
51
62
  # whenever an exception is raised (see #ensure_connected).
@@ -56,10 +67,7 @@ class Redis
56
67
  attr :callbacks
57
68
 
58
69
  def initialize
59
- @callbacks = Hash.new do |hash, key|
60
- hash[key] = ->(*_) {}
61
- end
62
-
70
+ @callbacks = {}
63
71
  yield(self)
64
72
  end
65
73
 
data/lib/redis/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Redis
4
- VERSION = '4.2.5'
4
+ VERSION = '5.0.7'
5
5
  end