redis 4.5.1 → 5.0.6

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 +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?