redis 4.5.1 → 5.0.6

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