redis 4.6.0 → 5.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -1
  3. data/README.md +77 -146
  4. data/lib/redis/client.rb +78 -615
  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/hashes.rb +6 -3
  9. data/lib/redis/commands/hyper_log_log.rb +1 -1
  10. data/lib/redis/commands/keys.rb +52 -26
  11. data/lib/redis/commands/lists.rb +10 -14
  12. data/lib/redis/commands/pubsub.rb +7 -9
  13. data/lib/redis/commands/server.rb +14 -14
  14. data/lib/redis/commands/sets.rb +42 -35
  15. data/lib/redis/commands/sorted_sets.rb +13 -3
  16. data/lib/redis/commands/streams.rb +12 -10
  17. data/lib/redis/commands/strings.rb +1 -0
  18. data/lib/redis/commands/transactions.rb +26 -3
  19. data/lib/redis/commands.rb +1 -8
  20. data/lib/redis/distributed.rb +99 -66
  21. data/lib/redis/errors.rb +10 -43
  22. data/lib/redis/hash_ring.rb +26 -26
  23. data/lib/redis/pipeline.rb +56 -203
  24. data/lib/redis/subscribe.rb +15 -9
  25. data/lib/redis/version.rb +1 -1
  26. data/lib/redis.rb +69 -175
  27. metadata +13 -57
  28. data/lib/redis/cluster/command.rb +0 -79
  29. data/lib/redis/cluster/command_loader.rb +0 -33
  30. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  31. data/lib/redis/cluster/node.rb +0 -120
  32. data/lib/redis/cluster/node_key.rb +0 -31
  33. data/lib/redis/cluster/node_loader.rb +0 -37
  34. data/lib/redis/cluster/option.rb +0 -93
  35. data/lib/redis/cluster/slot.rb +0 -86
  36. data/lib/redis/cluster/slot_loader.rb +0 -49
  37. data/lib/redis/cluster.rb +0 -315
  38. data/lib/redis/connection/command_helper.rb +0 -41
  39. data/lib/redis/connection/hiredis.rb +0 -68
  40. data/lib/redis/connection/registry.rb +0 -13
  41. data/lib/redis/connection/ruby.rb +0 -431
  42. data/lib/redis/connection/synchrony.rb +0 -148
  43. data/lib/redis/connection.rb +0 -11
@@ -4,24 +4,32 @@ require "delegate"
4
4
 
5
5
  class Redis
6
6
  class PipelinedConnection
7
- def initialize(pipeline)
7
+ attr_accessor :db
8
+
9
+ def initialize(pipeline, futures = [])
8
10
  @pipeline = pipeline
11
+ @futures = futures
9
12
  end
10
13
 
11
14
  include Commands
12
15
 
13
- def db
14
- @pipeline.db
15
- end
16
-
17
- def db=(db)
18
- @pipeline.db = db
19
- end
20
-
21
16
  def pipelined
22
17
  yield self
23
18
  end
24
19
 
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
31
+ end
32
+
25
33
  private
26
34
 
27
35
  def synchronize
@@ -29,191 +37,36 @@ class Redis
29
37
  end
30
38
 
31
39
  def send_command(command, &block)
32
- @pipeline.call(command, &block)
33
- end
34
-
35
- def send_blocking_command(command, timeout, &block)
36
- @pipeline.call_with_timeout(command, timeout, &block)
37
- end
38
- end
39
-
40
- class Pipeline
41
- REDIS_INTERNAL_PATH = File.expand_path("..", __dir__).freeze
42
- # Redis use MonitorMixin#synchronize and this class use DelegateClass which we want to filter out.
43
- # Both are in the stdlib so we can simply filter the entire stdlib out.
44
- STDLIB_PATH = File.expand_path("..", MonitorMixin.instance_method(:synchronize).source_location.first).freeze
45
-
46
- class << self
47
- def deprecation_warning(method, caller_locations) # :nodoc:
48
- callsite = caller_locations.find { |l| !l.path.start_with?(REDIS_INTERNAL_PATH, STDLIB_PATH) }
49
- callsite ||= caller_locations.last # The caller_locations should be large enough, but just in case.
50
- ::Redis.deprecate! <<~MESSAGE
51
- Pipelining commands on a Redis instance is deprecated and will be removed in Redis 5.0.0.
52
-
53
- redis.#{method} do
54
- redis.get("key")
55
- end
56
-
57
- should be replaced by
58
-
59
- redis.#{method} do |pipeline|
60
- pipeline.get("key")
61
- end
62
-
63
- (called from #{callsite}}
64
- MESSAGE
40
+ future = Future.new(command, block)
41
+ @pipeline.call_v(command) do |result|
42
+ future._set(result)
65
43
  end
66
- end
67
-
68
- attr_accessor :db
69
- attr_reader :client
70
-
71
- attr :futures
72
-
73
- def initialize(client)
74
- @client = client.is_a?(Pipeline) ? client.client : client
75
- @with_reconnect = true
76
- @shutdown = false
77
- @futures = []
78
- end
79
-
80
- def timeout
81
- client.timeout
82
- end
83
-
84
- def with_reconnect?
85
- @with_reconnect
86
- end
87
-
88
- def without_reconnect?
89
- !@with_reconnect
90
- end
91
-
92
- def shutdown?
93
- @shutdown
94
- end
95
-
96
- def empty?
97
- @futures.empty?
98
- end
99
-
100
- def call(command, timeout: nil, &block)
101
- # A pipeline that contains a shutdown should not raise ECONNRESET when
102
- # the connection is gone.
103
- @shutdown = true if command.first == :shutdown
104
- future = Future.new(command, block, timeout)
105
44
  @futures << future
106
45
  future
107
46
  end
108
47
 
109
- def call_with_timeout(command, timeout, &block)
110
- call(command, timeout: timeout, &block)
111
- end
112
-
113
- def call_pipeline(pipeline)
114
- @shutdown = true if pipeline.shutdown?
115
- @futures.concat(pipeline.futures)
116
- @db = pipeline.db
117
- nil
118
- end
119
-
120
- def commands
121
- @futures.map(&:_command)
122
- end
123
-
124
- def timeouts
125
- @futures.map(&:timeout)
126
- end
127
-
128
- def with_reconnect(val = true)
129
- @with_reconnect = false unless val
130
- yield
131
- end
132
-
133
- def without_reconnect(&blk)
134
- with_reconnect(false, &blk)
135
- end
136
-
137
- def finish(replies, &blk)
138
- if blk
139
- futures.each_with_index.map do |future, i|
140
- future._set(blk.call(replies[i]))
141
- end
142
- else
143
- futures.each_with_index.map do |future, i|
144
- future._set(replies[i])
145
- end
146
- end
147
- end
148
-
149
- class Multi < self
150
- def finish(replies)
151
- exec = replies.last
152
-
153
- return if exec.nil? # The transaction failed because of WATCH.
154
-
155
- # EXEC command failed.
156
- raise exec if exec.is_a?(CommandError)
157
-
158
- if exec.size < futures.size
159
- # Some command wasn't recognized by Redis.
160
- command_error = replies.detect { |r| r.is_a?(CommandError) }
161
- raise command_error
162
- end
163
-
164
- super(exec) do |reply|
165
- # Because an EXEC returns nested replies, hiredis won't be able to
166
- # convert an error reply to a CommandError instance itself. This is
167
- # specific to MULTI/EXEC, so we solve this here.
168
- reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply
169
- end
170
- end
171
-
172
- def timeouts
173
- if empty?
174
- []
175
- else
176
- [nil, *super, nil]
177
- end
178
- end
179
-
180
- def commands
181
- if empty?
182
- []
183
- else
184
- [[:multi]] + super + [[:exec]]
185
- 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)
186
52
  end
53
+ @futures << future
54
+ future
187
55
  end
188
56
  end
189
57
 
190
- class DeprecatedPipeline < DelegateClass(Pipeline)
191
- def initialize(pipeline)
192
- super(pipeline)
193
- @deprecation_displayed = false
58
+ class MultiConnection < PipelinedConnection
59
+ def multi
60
+ raise Redis::Error, "Can't nest multi transaction"
194
61
  end
195
62
 
196
- def __getobj__
197
- unless @deprecation_displayed
198
- Pipeline.deprecation_warning("pipelined", Kernel.caller_locations(1, 10))
199
- @deprecation_displayed = true
200
- end
201
- @delegate_dc_obj
202
- end
203
- end
63
+ private
204
64
 
205
- class DeprecatedMulti < DelegateClass(Pipeline::Multi)
206
- def initialize(pipeline)
207
- super(pipeline)
208
- @deprecation_displayed = false
209
- end
210
-
211
- def __getobj__
212
- unless @deprecation_displayed
213
- Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 10))
214
- @deprecation_displayed = true
215
- end
216
- @delegate_dc_obj
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)
217
70
  end
218
71
  end
219
72
 
@@ -226,23 +79,10 @@ class Redis
226
79
  class Future < BasicObject
227
80
  FutureNotReady = ::Redis::FutureNotReady.new
228
81
 
229
- attr_reader :timeout
230
-
231
- def initialize(command, transformation, timeout)
82
+ def initialize(command, coerce)
232
83
  @command = command
233
- @transformation = transformation
234
- @timeout = timeout
235
84
  @object = FutureNotReady
236
- end
237
-
238
- def ==(_other)
239
- message = +"The methods == and != are deprecated for Redis::Future and will be removed in 5.0.0"
240
- message << " - You probably meant to call .value == or .value !="
241
- message << " (#{::Kernel.caller(1, 1).first})\n"
242
-
243
- ::Redis.deprecate!(message)
244
-
245
- super
85
+ @coerce = coerce
246
86
  end
247
87
 
248
88
  def inspect
@@ -250,16 +90,12 @@ class Redis
250
90
  end
251
91
 
252
92
  def _set(object)
253
- @object = @transformation ? @transformation.call(object) : object
93
+ @object = @coerce ? @coerce.call(object) : object
254
94
  value
255
95
  end
256
96
 
257
- def _command
258
- @command
259
- end
260
-
261
97
  def value
262
- ::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
98
+ ::Kernel.raise(@object) if @object.is_a?(::StandardError)
263
99
  @object
264
100
  end
265
101
 
@@ -271,4 +107,21 @@ class Redis
271
107
  Future
272
108
  end
273
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
274
127
  end
@@ -6,8 +6,8 @@ class Redis
6
6
  @client = client
7
7
  end
8
8
 
9
- def call(command)
10
- @client.process([command])
9
+ def call_v(command)
10
+ @client.call_v(command)
11
11
  end
12
12
 
13
13
  def subscribe(*channels, &block)
@@ -27,11 +27,15 @@ class Redis
27
27
  end
28
28
 
29
29
  def unsubscribe(*channels)
30
- call([:unsubscribe, *channels])
30
+ call_v([:unsubscribe, *channels])
31
31
  end
32
32
 
33
33
  def punsubscribe(*channels)
34
- call([:punsubscribe, *channels])
34
+ call_v([:punsubscribe, *channels])
35
+ end
36
+
37
+ def close
38
+ @client.close
35
39
  end
36
40
 
37
41
  protected
@@ -39,13 +43,15 @@ class Redis
39
43
  def subscription(start, stop, channels, block, timeout = 0)
40
44
  sub = Subscription.new(&block)
41
45
 
42
- unsubscribed = false
46
+ @client.call_v([start, *channels])
47
+ while event = @client.next_event(timeout)
48
+ if event.is_a?(::RedisClient::CommandError)
49
+ raise Client::ERROR_MAPPING.fetch(event.class), event.message
50
+ end
43
51
 
44
- @client.call_loop([start, *channels], timeout) do |line|
45
- type, *rest = line
52
+ type, *rest = event
46
53
  sub.callbacks[type].call(*rest)
47
- unsubscribed = type == stop && rest.last == 0
48
- break if unsubscribed
54
+ break if type == stop && rest.last == 0
49
55
  end
50
56
  # No need to unsubscribe here. The real client closes the connection
51
57
  # whenever an exception is raised (see #ensure_connected).
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.6.0'
4
+ VERSION = '5.0.0.beta3'
5
5
  end