redis 4.4.0 → 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 +150 -0
  3. data/README.md +95 -160
  4. data/lib/redis/client.rb +84 -608
  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 +208 -70
  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 +109 -3546
  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 -108
  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 -93
  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 -291
  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,427 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "registry"
4
- require_relative "command_helper"
5
- require_relative "../errors"
6
- require "socket"
7
- require "timeout"
8
-
9
- begin
10
- require "openssl"
11
- rescue LoadError
12
- # Not all systems have OpenSSL support
13
- end
14
-
15
- class Redis
16
- module Connection
17
- module SocketMixin
18
- CRLF = "\r\n"
19
-
20
- def initialize(*args)
21
- super(*args)
22
-
23
- @timeout = @write_timeout = nil
24
- @buffer = "".dup
25
- end
26
-
27
- def timeout=(timeout)
28
- @timeout = (timeout if timeout && timeout > 0)
29
- end
30
-
31
- def write_timeout=(timeout)
32
- @write_timeout = (timeout if timeout && timeout > 0)
33
- end
34
-
35
- def read(nbytes)
36
- result = @buffer.slice!(0, nbytes)
37
-
38
- result << _read_from_socket(nbytes - result.bytesize) while result.bytesize < nbytes
39
-
40
- result
41
- end
42
-
43
- def gets
44
- while (crlf = @buffer.index(CRLF)).nil?
45
- @buffer << _read_from_socket(16_384)
46
- end
47
-
48
- @buffer.slice!(0, crlf + CRLF.bytesize)
49
- end
50
-
51
- def _read_from_socket(nbytes)
52
- loop do
53
- case chunk = read_nonblock(nbytes, exception: false)
54
- when :wait_readable
55
- unless wait_readable(@timeout)
56
- raise Redis::TimeoutError
57
- end
58
- when :wait_writable
59
- unless wait_writable(@timeout)
60
- raise Redis::TimeoutError
61
- end
62
- when nil
63
- raise Errno::ECONNRESET
64
- when String
65
- return chunk
66
- end
67
- end
68
- end
69
-
70
- def write(buffer)
71
- return super(buffer) unless @write_timeout
72
-
73
- bytes_to_write = buffer.bytesize
74
- total_bytes_written = 0
75
- loop do
76
- case bytes_written = write_nonblock(buffer, exception: false)
77
- when :wait_readable
78
- unless wait_readable(@write_timeout)
79
- raise Redis::TimeoutError
80
- end
81
- when :wait_writable
82
- unless wait_writable(@write_timeout)
83
- raise Redis::TimeoutError
84
- end
85
- when nil
86
- raise Errno::ECONNRESET
87
- when Integer
88
- total_bytes_written += bytes_written
89
-
90
- if total_bytes_written >= bytes_to_write
91
- return total_bytes_written
92
- end
93
-
94
- buffer = buffer.byteslice(bytes_written..-1)
95
- end
96
- end
97
- end
98
- end
99
-
100
- if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
101
-
102
- require "timeout"
103
-
104
- class TCPSocket < ::TCPSocket
105
- include SocketMixin
106
-
107
- def self.connect(host, port, timeout)
108
- Timeout.timeout(timeout) do
109
- sock = new(host, port)
110
- sock
111
- end
112
- rescue Timeout::Error
113
- raise TimeoutError
114
- end
115
- end
116
-
117
- if defined?(::UNIXSocket)
118
-
119
- class UNIXSocket < ::UNIXSocket
120
- include SocketMixin
121
-
122
- def self.connect(path, timeout)
123
- Timeout.timeout(timeout) do
124
- sock = new(path)
125
- sock
126
- end
127
- rescue Timeout::Error
128
- raise TimeoutError
129
- end
130
-
131
- # JRuby raises Errno::EAGAIN on #read_nonblock even when it
132
- # says it is readable (1.6.6, in both 1.8 and 1.9 mode).
133
- # Use the blocking #readpartial method instead.
134
-
135
- def _read_from_socket(nbytes)
136
- readpartial(nbytes)
137
- rescue EOFError
138
- raise Errno::ECONNRESET
139
- end
140
- end
141
-
142
- end
143
-
144
- else
145
-
146
- class TCPSocket < ::Socket
147
- include SocketMixin
148
-
149
- def self.connect_addrinfo(addrinfo, port, timeout)
150
- sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
151
- sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
152
-
153
- begin
154
- sock.connect_nonblock(sockaddr)
155
- rescue Errno::EINPROGRESS
156
- raise TimeoutError unless sock.wait_writable(timeout)
157
-
158
- begin
159
- sock.connect_nonblock(sockaddr)
160
- rescue Errno::EISCONN
161
- end
162
- end
163
-
164
- sock
165
- end
166
-
167
- def self.connect(host, port, timeout)
168
- # Don't pass AI_ADDRCONFIG as flag to getaddrinfo(3)
169
- #
170
- # From the man page for getaddrinfo(3):
171
- #
172
- # If hints.ai_flags includes the AI_ADDRCONFIG flag, then IPv4
173
- # addresses are returned in the list pointed to by res only if the
174
- # local system has at least one IPv4 address configured, and IPv6
175
- # addresses are returned only if the local system has at least one
176
- # IPv6 address configured. The loopback address is not considered
177
- # for this case as valid as a configured address.
178
- #
179
- # We do want the IPv6 loopback address to be returned if applicable,
180
- # even if it is the only configured IPv6 address on the machine.
181
- # Also see: https://github.com/redis/redis-rb/pull/394.
182
- addrinfo = ::Socket.getaddrinfo(host, nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
183
-
184
- # From the man page for getaddrinfo(3):
185
- #
186
- # Normally, the application should try using the addresses in the
187
- # order in which they are returned. The sorting function used
188
- # within getaddrinfo() is defined in RFC 3484 [...].
189
- #
190
- addrinfo.each_with_index do |ai, i|
191
- begin
192
- return connect_addrinfo(ai, port, timeout)
193
- rescue SystemCallError
194
- # Raise if this was our last attempt.
195
- raise if addrinfo.length == i + 1
196
- end
197
- end
198
- end
199
- end
200
-
201
- class UNIXSocket < ::Socket
202
- include SocketMixin
203
-
204
- def self.connect(path, timeout)
205
- sock = new(::Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
206
- sockaddr = ::Socket.pack_sockaddr_un(path)
207
-
208
- begin
209
- sock.connect_nonblock(sockaddr)
210
- rescue Errno::EINPROGRESS
211
- raise TimeoutError unless sock.wait_writable(timeout)
212
-
213
- begin
214
- sock.connect_nonblock(sockaddr)
215
- rescue Errno::EISCONN
216
- end
217
- end
218
-
219
- sock
220
- end
221
- end
222
-
223
- end
224
-
225
- if defined?(OpenSSL)
226
- class SSLSocket < ::OpenSSL::SSL::SSLSocket
227
- include SocketMixin
228
-
229
- unless method_defined?(:wait_readable)
230
- def wait_readable(timeout = nil)
231
- to_io.wait_readable(timeout)
232
- end
233
- end
234
-
235
- unless method_defined?(:wait_writable)
236
- def wait_writable(timeout = nil)
237
- to_io.wait_writable(timeout)
238
- end
239
- end
240
-
241
- def self.connect(host, port, timeout, ssl_params)
242
- # Note: this is using Redis::Connection::TCPSocket
243
- tcp_sock = TCPSocket.connect(host, port, timeout)
244
-
245
- ctx = OpenSSL::SSL::SSLContext.new
246
-
247
- # The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
248
- ctx.set_params(ssl_params || {})
249
-
250
- ssl_sock = new(tcp_sock, ctx)
251
- ssl_sock.hostname = host
252
-
253
- begin
254
- # Initiate the socket connection in the background. If it doesn't fail
255
- # immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
256
- # indicating the connection is in progress.
257
- # Unlike waiting for a tcp socket to connect, you can't time out ssl socket
258
- # connections during the connect phase properly, because IO.select only partially works.
259
- # Instead, you have to retry.
260
- ssl_sock.connect_nonblock
261
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
262
- if ssl_sock.wait_readable(timeout)
263
- retry
264
- else
265
- raise TimeoutError
266
- end
267
- rescue IO::WaitWritable
268
- if ssl_sock.wait_writable(timeout)
269
- retry
270
- else
271
- raise TimeoutError
272
- end
273
- end
274
-
275
- unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
276
- ctx.respond_to?(:verify_hostname) &&
277
- !ctx.verify_hostname
278
- )
279
- ssl_sock.post_connection_check(host)
280
- end
281
-
282
- ssl_sock
283
- end
284
- end
285
- end
286
-
287
- class Ruby
288
- include Redis::Connection::CommandHelper
289
-
290
- MINUS = "-"
291
- PLUS = "+"
292
- COLON = ":"
293
- DOLLAR = "$"
294
- ASTERISK = "*"
295
-
296
- def self.connect(config)
297
- if config[:scheme] == "unix"
298
- raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
299
-
300
- sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
301
- elsif config[:scheme] == "rediss" || config[:ssl]
302
- sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
303
- else
304
- sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
305
- end
306
-
307
- instance = new(sock)
308
- instance.timeout = config[:read_timeout]
309
- instance.write_timeout = config[:write_timeout]
310
- instance.set_tcp_keepalive config[:tcp_keepalive]
311
- instance.set_tcp_nodelay if sock.is_a? TCPSocket
312
- instance
313
- end
314
-
315
- if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
316
- def set_tcp_keepalive(keepalive)
317
- return unless keepalive.is_a?(Hash)
318
-
319
- @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
320
- @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time])
321
- @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl])
322
- @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes])
323
- end
324
-
325
- def get_tcp_keepalive
326
- {
327
- time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
328
- intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
329
- probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
330
- }
331
- end
332
- else
333
- def set_tcp_keepalive(keepalive); end
334
-
335
- def get_tcp_keepalive
336
- {
337
- }
338
- end
339
- end
340
-
341
- # disables Nagle's Algorithm, prevents multiple round trips with MULTI
342
- if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
343
- def set_tcp_nodelay
344
- @sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
345
- end
346
- else
347
- def set_tcp_nodelay; end
348
- end
349
-
350
- def initialize(sock)
351
- @sock = sock
352
- end
353
-
354
- def connected?
355
- !!@sock
356
- end
357
-
358
- def disconnect
359
- @sock.close
360
- rescue
361
- ensure
362
- @sock = nil
363
- end
364
-
365
- def timeout=(timeout)
366
- @sock.timeout = timeout if @sock.respond_to?(:timeout=)
367
- end
368
-
369
- def write_timeout=(timeout)
370
- @sock.write_timeout = timeout
371
- end
372
-
373
- def write(command)
374
- @sock.write(build_command(command))
375
- end
376
-
377
- def read
378
- line = @sock.gets
379
- reply_type = line.slice!(0, 1)
380
- format_reply(reply_type, line)
381
- rescue Errno::EAGAIN
382
- raise TimeoutError
383
- end
384
-
385
- def format_reply(reply_type, line)
386
- case reply_type
387
- when MINUS then format_error_reply(line)
388
- when PLUS then format_status_reply(line)
389
- when COLON then format_integer_reply(line)
390
- when DOLLAR then format_bulk_reply(line)
391
- when ASTERISK then format_multi_bulk_reply(line)
392
- else raise ProtocolError, reply_type
393
- end
394
- end
395
-
396
- def format_error_reply(line)
397
- CommandError.new(line.strip)
398
- end
399
-
400
- def format_status_reply(line)
401
- line.strip
402
- end
403
-
404
- def format_integer_reply(line)
405
- line.to_i
406
- end
407
-
408
- def format_bulk_reply(line)
409
- bulklen = line.to_i
410
- return if bulklen == -1
411
-
412
- reply = encode(@sock.read(bulklen))
413
- @sock.read(2) # Discard CRLF.
414
- reply
415
- end
416
-
417
- def format_multi_bulk_reply(line)
418
- n = line.to_i
419
- return if n == -1
420
-
421
- Array.new(n) { read }
422
- end
423
- end
424
- end
425
- end
426
-
427
- Redis::Connection.drivers << Redis::Connection::Ruby
@@ -1,146 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "command_helper"
4
- require_relative "registry"
5
- require_relative "../errors"
6
- require "em-synchrony"
7
- require "hiredis/reader"
8
-
9
- Kernel.warn(
10
- "The redis synchrony driver is deprecated and will be removed in redis-rb 5.0. " \
11
- "We're looking for people to maintain it as a separate gem, see https://github.com/redis/redis-rb/issues/915"
12
- )
13
-
14
- class Redis
15
- module Connection
16
- class RedisClient < EventMachine::Connection
17
- include EventMachine::Deferrable
18
-
19
- attr_accessor :timeout
20
-
21
- def post_init
22
- @req = nil
23
- @connected = false
24
- @reader = ::Hiredis::Reader.new
25
- end
26
-
27
- def connection_completed
28
- @connected = true
29
- succeed
30
- end
31
-
32
- def connected?
33
- @connected
34
- end
35
-
36
- def receive_data(data)
37
- @reader.feed(data)
38
-
39
- loop do
40
- begin
41
- reply = @reader.gets
42
- rescue RuntimeError => err
43
- @req.fail [:error, ProtocolError.new(err.message)]
44
- break
45
- end
46
-
47
- break if reply == false
48
-
49
- reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
50
- @req.succeed [:reply, reply]
51
- end
52
- end
53
-
54
- def read
55
- @req = EventMachine::DefaultDeferrable.new
56
- @req.timeout(@timeout, :timeout) if @timeout > 0
57
- EventMachine::Synchrony.sync @req
58
- end
59
-
60
- def send(data)
61
- callback { send_data data }
62
- end
63
-
64
- def unbind
65
- @connected = false
66
- if @req
67
- @req.fail [:error, Errno::ECONNRESET]
68
- @req = nil
69
- else
70
- fail
71
- end
72
- end
73
- end
74
-
75
- class Synchrony
76
- include Redis::Connection::CommandHelper
77
-
78
- def self.connect(config)
79
- if config[:scheme] == "unix"
80
- begin
81
- conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
82
- rescue RuntimeError => e
83
- if e.message == "no connection"
84
- raise Errno::ECONNREFUSED
85
- else
86
- raise e
87
- end
88
- end
89
- elsif config[:scheme] == "rediss" || config[:ssl]
90
- raise NotImplementedError, "SSL not supported by synchrony driver"
91
- else
92
- conn = EventMachine.connect(config[:host], config[:port], RedisClient) do |c|
93
- c.pending_connect_timeout = [config[:connect_timeout], 0.1].max
94
- end
95
- end
96
-
97
- fiber = Fiber.current
98
- conn.callback { fiber.resume }
99
- conn.errback { fiber.resume :refused }
100
-
101
- raise Errno::ECONNREFUSED if Fiber.yield == :refused
102
-
103
- instance = new(conn)
104
- instance.timeout = config[:read_timeout]
105
- instance
106
- end
107
-
108
- def initialize(connection)
109
- @connection = connection
110
- end
111
-
112
- def connected?
113
- @connection&.connected?
114
- end
115
-
116
- def timeout=(timeout)
117
- @connection.timeout = timeout
118
- end
119
-
120
- def disconnect
121
- @connection.close_connection
122
- @connection = nil
123
- end
124
-
125
- def write(command)
126
- @connection.send(build_command(command))
127
- end
128
-
129
- def read
130
- type, payload = @connection.read
131
-
132
- if type == :reply
133
- payload
134
- elsif type == :error
135
- raise payload
136
- elsif type == :timeout
137
- raise TimeoutError
138
- else
139
- raise "Unknown type #{type.inspect}"
140
- end
141
- end
142
- end
143
- end
144
- end
145
-
146
- Redis::Connection.drivers << Redis::Connection::Synchrony
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "connection/registry"
4
-
5
- # If a connection driver was required before this file, the array
6
- # Redis::Connection.drivers will contain one or more classes. The last driver
7
- # in this array will be used as default driver. If this array is empty, we load
8
- # the plain Ruby driver as our default. Another driver can be required at a
9
- # later point in time, causing it to be the last element of the #drivers array
10
- # and therefore be chosen by default.
11
- require_relative "connection/ruby" if Redis::Connection.drivers.empty?