redis 4.1.1 → 4.2.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fba6921160b00d34cc11548defceaaee4fc9c181
4
- data.tar.gz: e5eac2ace205355e4890084cc96fe98c651e8d30
2
+ SHA256:
3
+ metadata.gz: 862e8133262fead707e4ca317b29d0e35425e3a7861ea1a52033bc91ba61bf6d
4
+ data.tar.gz: 5b2b32250d783fe58b0af54cc386f2568241644a0badc0b7247bb29b1ac94a93
5
5
  SHA512:
6
- metadata.gz: 5e88bba869f876bc046935479283f108b46107ab00e7ab60487ca524977635dbdc9b0dcfd037a9b057e43765975c26cdc3a0ceea95aefacf608041a18b50f14e
7
- data.tar.gz: 91360e891b269c8ecb962414f29d023ace19af98e929d7f7680590dcb8520a87deb6c2a47ba27da0ea16eb47ee90d4c8bdf2be894a40c00820f9c07479188cc2
6
+ metadata.gz: 2aab289f4f22b2f3a804ca7b0da4cf95e352a8d246611490d8d803edebbbc7e7c299355cd0b90e6f3ac8bc0d11e9bf3792a328c95847d32e1d984729afe66ed2
7
+ data.tar.gz: d9ec8ba4d314d099e909cdddf6dfb4c3c14b853b46f45c4909ac3ba10fb1880bd9a7b0465257fa91f9a4ea6c9f82723c16c177810ac15c89fab3790d7af31ad0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,74 @@
1
1
  # Unreleased
2
2
 
3
+ # 4.2.5
4
+
5
+ * Optimize the ruby connector write buffering. See #964.
6
+
7
+ # 4.2.4
8
+
9
+ * Fix bytesize calculations in the ruby connector, and work on a copy of the buffer. Fix #961, #962.
10
+
11
+ # 4.2.3
12
+
13
+ * Use io/wait instead of IO.select in the ruby connector. See #960.
14
+ * Use exception free non blocking IOs in the ruby connector. See #926.
15
+ * Prevent corruption of the client when an interrupt happen during inside a pipeline block. See #945.
16
+
17
+ # 4.2.2
18
+
19
+ * Fix `WATCH` support for `Redis::Distributed`. See #941.
20
+ * Fix handling of empty stream responses. See #905, #929.
21
+
22
+ # 4.2.1
23
+
24
+ * Fix `exists?` returning an actual boolean when called with multiple keys. See #918.
25
+ * Setting `Redis.exists_returns_integer = false` disables warning message about new behaviour. See #920.
26
+
27
+ # 4.2.0
28
+
29
+ * Convert commands to accept keyword arguments rather than option hashes. This both help catching typos, and reduce needless allocations.
30
+ * Deprecate the synchrony driver. It will be removed in 5.0 and hopefully maintained as a separate gem. See #915.
31
+ * Make `Redis#exists` variadic, will return an Integer if called with multiple keys.
32
+ * Add `Redis#exists?` to get a Boolean if any of the keys exists.
33
+ * `Redis#exists` when called with a single key will warn that future versions will return an Integer.
34
+ Set `Redis.exists_returns_integer = true` to opt-in to the new behavior.
35
+ * Support `keepttl` ooption in `set`. See #913.
36
+ * Optimized initialization of Redis::Cluster. See #912.
37
+ * Accept sentinel options even with string key. See #599.
38
+ * Verify TLS connections by default. See #900.
39
+ * Make `Redis#hset` variadic. It now returns an integer, not a boolean. See #910.
40
+
41
+ # 4.1.4
42
+
43
+ * Alias `Redis#disconnect` as `#close`. See #901.
44
+ * Handle clusters with multiple slot ranges. See #894.
45
+ * Fix password authentication to a redis cluster. See #889.
46
+ * Handle recursive MOVED responses. See #882.
47
+ * Increase buffer size in the ruby connector. See #880.
48
+ * Fix thread safety of `Redis.queue`. See #878.
49
+ * Deprecate `Redis::Future#==` as it's likely to be a mistake. See #876.
50
+ * Support `KEEPTTL` option for SET command. See #913.
51
+
52
+ # 4.1.3
53
+
54
+ * Fix the client hanging forever when connecting with SSL to a non-SSL server. See #835.
55
+
56
+ # 4.1.2
57
+
58
+ * Fix several authentication problems with sentinel. See #850 and #856.
59
+ * Explicitly drop Ruby 2.2 support.
60
+
61
+
62
+ # 4.1.1
63
+
64
+ * Fix error handling in multi blocks. See #754.
65
+ * Fix geoadd to accept arrays like georadius and georadiusbymember. See #841.
66
+ * Fix georadius command failing when long == lat. See #841.
67
+ * Fix timeout error in xread block: 0. See #837.
68
+ * Fix incompatibility issue with redis-objects. See #834.
69
+ * Properly handle Errno::EADDRNOTAVAIL on connect.
70
+ * Fix password authentication to sentinel instances. See #813.
71
+
3
72
  # 4.1.0
4
73
 
5
74
  * Add Redis Cluster support. See #716.
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
- # redis-rb [![Build Status][travis-image]][travis-link] [![Inline docs][inchpages-image]][inchpages-link]
1
+ # redis-rb [![Build Status][travis-image]][travis-link] [![Inline docs][inchpages-image]][inchpages-link] ![](https://github.com/redis/redis-rb/workflows/Test/badge.svg?branch=master)
2
2
 
3
3
  A Ruby client that tries to match [Redis][redis-home]' API one-to-one, while still
4
4
  providing an idiomatic interface.
5
5
 
6
+ See [RubyDoc.info][rubydoc] for the API docs of the latest published gem.
6
7
 
7
8
  ## Getting started
8
9
 
@@ -34,6 +35,9 @@ You can also specify connection options as a [`redis://` URL][redis-url]:
34
35
  redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
35
36
  ```
36
37
 
38
+ The client expects passwords with special chracters to be URL-encoded (i.e.
39
+ `CGI.escape(password)`).
40
+
37
41
  By default, the client will try to read the `REDIS_URL` environment variable
38
42
  and use that as URL to connect to. The above statement is therefore equivalent
39
43
  to setting this environment variable and calling `Redis.new` without arguments.
@@ -95,6 +99,15 @@ but a few so that if one is down the client will try the next one. The client
95
99
  is able to remember the last Sentinel that was able to reply correctly and will
96
100
  use it for the next requests.
97
101
 
102
+ If you want to [authenticate](https://redis.io/topics/sentinel#configuring-sentinel-instances-with-authentication) Sentinel itself, you must specify the `password` option per instance.
103
+
104
+ ```ruby
105
+ SENTINELS = [{ host: '127.0.0.1', port: 26380, password: 'mysecret' },
106
+ { host: '127.0.0.1', port: 26381, password: 'mysecret' }]
107
+
108
+ redis = Redis.new(host: 'mymaster', sentinels: SENTINELS, role: :master)
109
+ ```
110
+
98
111
  ## Cluster support
99
112
 
100
113
  `redis-rb` supports [clustering](https://redis.io/topics/cluster-spec).
@@ -133,12 +146,13 @@ redis.mget('{key}1', '{key}2')
133
146
  ```
134
147
 
135
148
  * The client automatically reconnects after a failover occurred, but the caller is responsible for handling errors while it is happening.
149
+ * The client support permanent node failures, and will reroute requests to promoted slaves.
136
150
  * The client supports `MOVED` and `ASK` redirections transparently.
137
151
 
138
152
  ## Storing objects
139
153
 
140
- Redis only stores strings as values. If you want to store an object, you
141
- can use a serialization mechanism such as JSON:
154
+ Redis "string" types can be used to store serialized Ruby objects, for
155
+ example with JSON:
142
156
 
143
157
  ```ruby
144
158
  require "json"
@@ -251,6 +265,7 @@ All timeout values are specified in seconds.
251
265
  When using pub/sub, you can subscribe to a channel using a timeout as well:
252
266
 
253
267
  ```ruby
268
+ redis = Redis.new(reconnect_attempts: 0)
254
269
  redis.subscribe_with_timeout(5, "news") do |on|
255
270
  on.message do |channel, message|
256
271
  # ...
@@ -312,7 +327,7 @@ This library supports natively terminating client side SSL/TLS connections
312
327
  when talking to Redis via a server-side proxy such as [stunnel], [hitch],
313
328
  or [ghostunnel].
314
329
 
315
- To enable SSL support, pass the `:ssl => :true` option when configuring the
330
+ To enable SSL support, pass the `:ssl => true` option when configuring the
316
331
  Redis client, or pass in `:url => "rediss://..."` (like HTTPS for Redis).
317
332
  You will also need to pass in an `:ssl_params => { ... }` hash used to
318
333
  configure the `OpenSSL::SSL::SSLContext` object used for the connection:
@@ -427,6 +442,10 @@ redis = Redis.new(:driver => :synchrony)
427
442
  This library is tested against recent Ruby and Redis versions.
428
443
  Check [Travis][travis-link] for the exact versions supported.
429
444
 
445
+ ## See Also
446
+
447
+ - [async-redis](https://github.com/socketry/async-redis) — An [async](https://github.com/socketry/async) compatible Redis client.
448
+
430
449
  ## Contributors
431
450
 
432
451
  Several people contributed to redis-rb, but we would like to especially
@@ -437,7 +456,7 @@ client and evangelized Redis in Rubyland. Thank you, Ezra.
437
456
  ## Contributing
438
457
 
439
458
  [Fork the project](https://github.com/redis/redis-rb) and send pull
440
- requests. You can also ask for help at `#redis-rb` on Freenode.
459
+ requests.
441
460
 
442
461
 
443
462
  [inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg
data/lib/redis/client.rb CHANGED
@@ -6,24 +6,30 @@ require "cgi"
6
6
 
7
7
  class Redis
8
8
  class Client
9
-
9
+ # Defaults are also used for converting string keys to symbols.
10
10
  DEFAULTS = {
11
- :url => lambda { ENV["REDIS_URL"] },
12
- :scheme => "redis",
13
- :host => "127.0.0.1",
14
- :port => 6379,
15
- :path => nil,
16
- :timeout => 5.0,
17
- :password => nil,
18
- :db => 0,
19
- :driver => nil,
20
- :id => nil,
21
- :tcp_keepalive => 0,
22
- :reconnect_attempts => 1,
23
- :reconnect_delay => 0,
24
- :reconnect_delay_max => 0.5,
25
- :inherit_socket => false
26
- }
11
+ url: -> { ENV["REDIS_URL"] },
12
+ scheme: "redis",
13
+ host: "127.0.0.1",
14
+ port: 6379,
15
+ path: nil,
16
+ read_timeout: nil,
17
+ write_timeout: nil,
18
+ connect_timeout: nil,
19
+ timeout: 5.0,
20
+ password: nil,
21
+ db: 0,
22
+ driver: nil,
23
+ id: nil,
24
+ tcp_keepalive: 0,
25
+ reconnect_attempts: 1,
26
+ reconnect_delay: 0,
27
+ reconnect_delay_max: 0.5,
28
+ inherit_socket: false,
29
+ logger: nil,
30
+ sentinels: nil,
31
+ role: nil
32
+ }.freeze
27
33
 
28
34
  attr_reader :options
29
35
 
@@ -89,7 +95,7 @@ class Redis
89
95
  @pending_reads = 0
90
96
 
91
97
  @connector =
92
- if options.include?(:sentinels)
98
+ if !@options[:sentinels].nil?
93
99
  Connector::Sentinel.new(@options)
94
100
  elsif options.include?(:connector) && options[:connector].respond_to?(:new)
95
101
  options.delete(:connector).new(@options)
@@ -166,6 +172,7 @@ class Redis
166
172
  end
167
173
  rescue ConnectionError => e
168
174
  return nil if pipeline.shutdown?
175
+
169
176
  # Assume the pipeline was sent in one piece, but execution of
170
177
  # SHUTDOWN caused none of the replies for commands that were executed
171
178
  # prior to it from coming back around.
@@ -244,12 +251,13 @@ class Redis
244
251
  end
245
252
 
246
253
  def connected?
247
- !! (connection && connection.connected?)
254
+ !!(connection && connection.connected?)
248
255
  end
249
256
 
250
257
  def disconnect
251
258
  connection.disconnect if connected?
252
259
  end
260
+ alias close disconnect
253
261
 
254
262
  def reconnect
255
263
  disconnect
@@ -300,30 +308,27 @@ class Redis
300
308
  with_socket_timeout(0, &blk)
301
309
  end
302
310
 
303
- def with_reconnect(val=true)
304
- begin
305
- original, @reconnect = @reconnect, val
306
- yield
307
- ensure
308
- @reconnect = original
309
- end
311
+ def with_reconnect(val = true)
312
+ original, @reconnect = @reconnect, val
313
+ yield
314
+ ensure
315
+ @reconnect = original
310
316
  end
311
317
 
312
318
  def without_reconnect(&blk)
313
319
  with_reconnect(false, &blk)
314
320
  end
315
321
 
316
- protected
322
+ protected
317
323
 
318
324
  def logging(commands)
319
- return yield unless @logger && @logger.debug?
325
+ return yield unless @logger&.debug?
320
326
 
321
327
  begin
322
328
  commands.each do |name, *args|
323
329
  logged_args = args.map do |a|
324
- case
325
- when a.respond_to?(:inspect) then a.inspect
326
- when a.respond_to?(:to_s) then a.to_s
330
+ if a.respond_to?(:inspect) then a.inspect
331
+ elsif a.respond_to?(:to_s) then a.to_s
327
332
  else
328
333
  # handle poorly-behaved descendants of BasicObject
329
334
  klass = a.instance_exec { (class << self; self end).superclass }
@@ -357,9 +362,9 @@ class Redis
357
362
  Errno::ENETUNREACH,
358
363
  Errno::ENOENT,
359
364
  Errno::ETIMEDOUT,
360
- Errno::EINVAL
365
+ Errno::EINVAL => error
361
366
 
362
- raise CannotConnectError, "Error connecting to Redis on #{location} (#{$!.class})"
367
+ raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
363
368
  end
364
369
 
365
370
  def ensure_connected
@@ -373,9 +378,9 @@ class Redis
373
378
  if connected?
374
379
  unless inherit_socket? || Process.pid == @pid
375
380
  raise InheritedError,
376
- "Tried to use a connection from a child process without reconnecting. " +
377
- "You need to reconnect to Redis after forking " +
378
- "or set :inherit_socket to true."
381
+ "Tried to use a connection from a child process without reconnecting. " \
382
+ "You need to reconnect to Redis after forking " \
383
+ "or set :inherit_socket to true."
379
384
  end
380
385
  else
381
386
  connect
@@ -386,7 +391,7 @@ class Redis
386
391
  disconnect
387
392
 
388
393
  if attempts <= @options[:reconnect_attempts] && @reconnect
389
- sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
394
+ sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
390
395
  @options[:reconnect_delay_max]].min
391
396
 
392
397
  Kernel.sleep(sleep_t)
@@ -408,16 +413,14 @@ class Redis
408
413
 
409
414
  defaults.keys.each do |key|
410
415
  # Fill in defaults if needed
411
- if defaults[key].respond_to?(:call)
412
- defaults[key] = defaults[key].call
413
- end
416
+ defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
414
417
 
415
418
  # Symbolize only keys that are needed
416
- options[key] = options[key.to_s] if options.has_key?(key.to_s)
419
+ options[key] = options[key.to_s] if options.key?(key.to_s)
417
420
  end
418
421
 
419
422
  url = options[:url]
420
- url = defaults[:url] if url == nil
423
+ url = defaults[:url] if url.nil?
421
424
 
422
425
  # Override defaults from URL if given
423
426
  if url
@@ -426,7 +429,7 @@ class Redis
426
429
  uri = URI(url)
427
430
 
428
431
  if uri.scheme == "unix"
429
- defaults[:path] = uri.path
432
+ defaults[:path] = uri.path
430
433
  elsif uri.scheme == "redis" || uri.scheme == "rediss"
431
434
  defaults[:scheme] = uri.scheme
432
435
  defaults[:host] = uri.host if uri.host
@@ -457,7 +460,7 @@ class Redis
457
460
  options[:port] = options[:port].to_i
458
461
  end
459
462
 
460
- if options.has_key?(:timeout)
463
+ if options.key?(:timeout)
461
464
  options[:connect_timeout] ||= options[:timeout]
462
465
  options[:read_timeout] ||= options[:timeout]
463
466
  options[:write_timeout] ||= options[:timeout]
@@ -476,7 +479,7 @@ class Redis
476
479
 
477
480
  case options[:tcp_keepalive]
478
481
  when Hash
479
- [:time, :intvl, :probes].each do |key|
482
+ %i[time intvl probes].each do |key|
480
483
  unless options[:tcp_keepalive][key].is_a?(Integer)
481
484
  raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
482
485
  end
@@ -484,13 +487,13 @@ class Redis
484
487
 
485
488
  when Integer
486
489
  if options[:tcp_keepalive] >= 60
487
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2}
490
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
488
491
 
489
492
  elsif options[:tcp_keepalive] >= 30
490
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2}
493
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
491
494
 
492
495
  elsif options[:tcp_keepalive] >= 5
493
- options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1}
496
+ options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
494
497
  end
495
498
  end
496
499
 
@@ -502,14 +505,14 @@ class Redis
502
505
  def _parse_driver(driver)
503
506
  driver = driver.to_s if driver.is_a?(Symbol)
504
507
 
505
- if driver.kind_of?(String)
508
+ if driver.is_a?(String)
506
509
  begin
507
510
  require_relative "connection/#{driver}"
508
- rescue LoadError, NameError => e
511
+ rescue LoadError, NameError
509
512
  begin
510
513
  require "connection/#{driver}"
511
- rescue LoadError, NameError => e
512
- raise RuntimeError, "Cannot load driver #{driver.inspect}: #{e.message}"
514
+ rescue LoadError, NameError => error
515
+ raise "Cannot load driver #{driver.inspect}: #{error.message}"
513
516
  end
514
517
  end
515
518
 
@@ -528,8 +531,7 @@ class Redis
528
531
  @options
529
532
  end
530
533
 
531
- def check(client)
532
- end
534
+ def check(client); end
533
535
 
534
536
  class Sentinel < Connector
535
537
  def initialize(options)
@@ -538,7 +540,7 @@ class Redis
538
540
  @options[:db] = DEFAULTS.fetch(:db)
539
541
 
540
542
  @sentinels = @options.delete(:sentinels).dup
541
- @role = @options.fetch(:role, "master").to_s
543
+ @role = (@options[:role] || "master").to_s
542
544
  @master = @options[:host]
543
545
  end
544
546
 
@@ -561,13 +563,13 @@ class Redis
561
563
 
562
564
  def resolve
563
565
  result = case @role
564
- when "master"
565
- resolve_master
566
- when "slave"
567
- resolve_slave
568
- else
569
- raise ArgumentError, "Unknown instance role #{@role}"
570
- end
566
+ when "master"
567
+ resolve_master
568
+ when "slave"
569
+ resolve_slave
570
+ else
571
+ raise ArgumentError, "Unknown instance role #{@role}"
572
+ end
571
573
 
572
574
  result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
573
575
  end
@@ -575,10 +577,11 @@ class Redis
575
577
  def sentinel_detect
576
578
  @sentinels.each do |sentinel|
577
579
  client = Client.new(@options.merge({
578
- :host => sentinel[:host],
579
- :port => sentinel[:port],
580
- :reconnect_attempts => 0,
581
- }))
580
+ host: sentinel[:host] || sentinel["host"],
581
+ port: sentinel[:port] || sentinel["port"],
582
+ password: sentinel[:password] || sentinel["password"],
583
+ reconnect_attempts: 0
584
+ }))
582
585
 
583
586
  begin
584
587
  if result = yield(client)
@@ -595,17 +598,12 @@ class Redis
595
598
  end
596
599
 
597
600
  raise CannotConnectError, "No sentinels available."
598
- rescue Redis::CommandError => err
599
- # this feature is only available starting with Redis 5.0.1
600
- raise unless err.message.start_with?('ERR unknown command `auth`')
601
- @options[:password] = DEFAULTS.fetch(:password)
602
- retry
603
601
  end
604
602
 
605
603
  def resolve_master
606
604
  sentinel_detect do |client|
607
605
  if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
608
- {:host => reply[0], :port => reply[1]}
606
+ { host: reply[0], port: reply[1] }
609
607
  end
610
608
  end
611
609
  end
@@ -623,7 +621,7 @@ class Redis
623
621
  slave = slaves.sample
624
622
  {
625
623
  host: slave.fetch('ip'),
626
- port: slave.fetch('port'),
624
+ port: slave.fetch('port')
627
625
  }
628
626
  end
629
627
  end
@@ -39,6 +39,7 @@ class Redis
39
39
  def call_master(command, &block)
40
40
  try_map do |node_key, client|
41
41
  next if slave?(node_key)
42
+
42
43
  client.call(command, &block)
43
44
  end.values
44
45
  end
@@ -48,6 +49,7 @@ class Redis
48
49
 
49
50
  try_map do |node_key, client|
50
51
  next if master?(node_key)
52
+
51
53
  client.call(command, &block)
52
54
  end.values
53
55
  end
@@ -97,6 +99,7 @@ class Redis
97
99
  end
98
100
 
99
101
  return results if errors.empty?
102
+
100
103
  raise CommandErrorCollection, errors
101
104
  end
102
105
  end
@@ -6,17 +6,13 @@ class Redis
6
6
  # It is different from node id.
7
7
  # Node id is internal identifying code in Redis Cluster.
8
8
  module NodeKey
9
- DEFAULT_SCHEME = 'redis'
10
- SECURE_SCHEME = 'rediss'
11
9
  DELIMITER = ':'
12
10
 
13
11
  module_function
14
12
 
15
- def to_node_urls(node_keys, secure:)
16
- scheme = secure ? SECURE_SCHEME : DEFAULT_SCHEME
17
- node_keys
18
- .map { |k| k.split(DELIMITER) }
19
- .map { |k| URI::Generic.build(scheme: scheme, host: k[0], port: k[1].to_i).to_s }
13
+ def optionize(node_key)
14
+ host, port = split(node_key)
15
+ { host: host, port: port }
20
16
  end
21
17
 
22
18
  def split(node_key)
@@ -15,36 +15,35 @@ class Redis
15
15
  def initialize(options)
16
16
  options = options.dup
17
17
  node_addrs = options.delete(:cluster)
18
- @node_uris = build_node_uris(node_addrs)
18
+ @node_opts = build_node_options(node_addrs)
19
19
  @replica = options.delete(:replica) == true
20
+ add_common_node_option_if_needed(options, @node_opts, :scheme)
21
+ add_common_node_option_if_needed(options, @node_opts, :password)
20
22
  @options = options
21
23
  end
22
24
 
23
25
  def per_node_key
24
- @node_uris.map { |uri| [NodeKey.build_from_uri(uri), @options.merge(url: uri.to_s)] }
26
+ @node_opts.map { |opt| [NodeKey.build_from_host_port(opt[:host], opt[:port]), @options.merge(opt)] }
25
27
  .to_h
26
28
  end
27
29
 
28
- def secure?
29
- @node_uris.any? { |uri| uri.scheme == SECURE_SCHEME } || @options[:ssl_params] || false
30
- end
31
-
32
30
  def use_replica?
33
31
  @replica
34
32
  end
35
33
 
36
34
  def update_node(addrs)
37
- @node_uris = build_node_uris(addrs)
35
+ @node_opts = build_node_options(addrs)
38
36
  end
39
37
 
40
38
  def add_node(host, port)
41
- @node_uris << parse_node_hash(host: host, port: port)
39
+ @node_opts << { host: host, port: port }
42
40
  end
43
41
 
44
42
  private
45
43
 
46
- def build_node_uris(addrs)
44
+ def build_node_options(addrs)
47
45
  raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
46
+
48
47
  addrs.map { |addr| parse_node_addr(addr) }
49
48
  end
50
49
 
@@ -53,7 +52,7 @@ class Redis
53
52
  when String
54
53
  parse_node_url(addr)
55
54
  when Hash
56
- parse_node_hash(addr)
55
+ parse_node_option(addr)
57
56
  else
58
57
  raise InvalidClientOptionError, 'Redis option of `cluster` must includes String or Hash'
59
58
  end
@@ -62,15 +61,29 @@ class Redis
62
61
  def parse_node_url(addr)
63
62
  uri = URI(addr)
64
63
  raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
65
- uri
64
+
65
+ db = uri.path.split('/')[1]&.to_i
66
+ { scheme: uri.scheme, password: uri.password, host: uri.host, port: uri.port, db: db }.reject { |_, v| v.nil? }
66
67
  rescue URI::InvalidURIError => err
67
68
  raise InvalidClientOptionError, err.message
68
69
  end
69
70
 
70
- def parse_node_hash(addr)
71
+ def parse_node_option(addr)
71
72
  addr = addr.map { |k, v| [k.to_sym, v] }.to_h
72
- raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys' if addr.values_at(:host, :port).any?(&:nil?)
73
- URI::Generic.build(scheme: DEFAULT_SCHEME, host: addr[:host], port: addr[:port].to_i)
73
+ if addr.values_at(:host, :port).any?(&:nil?)
74
+ raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys'
75
+ end
76
+
77
+ addr
78
+ end
79
+
80
+ # Redis cluster node returns only host and port information.
81
+ # So we should complement additional information such as:
82
+ # scheme, password and so on.
83
+ def add_common_node_option_if_needed(options, node_opts, key)
84
+ return options if options[key].nil? && node_opts.first[key].nil?
85
+
86
+ options[key] ||= node_opts.first[key]
74
87
  end
75
88
  end
76
89
  end