redis 4.6.0 → 5.0.0.beta3

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 (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