redis 4.4.0 → 5.0.7

Sign up to get free protection for your applications and to get access to all the features.
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?