redis 4.1.2 → 4.2.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -1
- data/README.md +14 -5
- data/lib/redis.rb +385 -343
- data/lib/redis/client.rb +66 -69
- data/lib/redis/cluster.rb +13 -4
- data/lib/redis/cluster/node.rb +3 -0
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/option.rb +27 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +4 -4
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/connection/command_helper.rb +3 -2
- data/lib/redis/connection/hiredis.rb +4 -3
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +47 -58
- data/lib/redis/connection/synchrony.rb +9 -4
- data/lib/redis/distributed.rb +117 -62
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +16 -3
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- metadata +14 -9
    
        data/lib/redis/client.rb
    CHANGED
    
    | @@ -6,24 +6,25 @@ require "cgi" | |
| 6 6 |  | 
| 7 7 | 
             
            class Redis
         | 
| 8 8 | 
             
              class Client
         | 
| 9 | 
            -
             | 
| 10 9 | 
             
                DEFAULTS = {
         | 
| 11 | 
            -
                  : | 
| 12 | 
            -
                  : | 
| 13 | 
            -
                  : | 
| 14 | 
            -
                  : | 
| 15 | 
            -
                  : | 
| 16 | 
            -
                  : | 
| 17 | 
            -
                  : | 
| 18 | 
            -
                  : | 
| 19 | 
            -
                  : | 
| 20 | 
            -
                  : | 
| 21 | 
            -
                  : | 
| 22 | 
            -
                  : | 
| 23 | 
            -
                  : | 
| 24 | 
            -
                  : | 
| 25 | 
            -
                  : | 
| 26 | 
            -
             | 
| 10 | 
            +
                  url: -> { ENV["REDIS_URL"] },
         | 
| 11 | 
            +
                  scheme: "redis",
         | 
| 12 | 
            +
                  host: "127.0.0.1",
         | 
| 13 | 
            +
                  port: 6379,
         | 
| 14 | 
            +
                  path: nil,
         | 
| 15 | 
            +
                  timeout: 5.0,
         | 
| 16 | 
            +
                  password: nil,
         | 
| 17 | 
            +
                  db: 0,
         | 
| 18 | 
            +
                  driver: nil,
         | 
| 19 | 
            +
                  id: nil,
         | 
| 20 | 
            +
                  tcp_keepalive: 0,
         | 
| 21 | 
            +
                  reconnect_attempts: 1,
         | 
| 22 | 
            +
                  reconnect_delay: 0,
         | 
| 23 | 
            +
                  reconnect_delay_max: 0.5,
         | 
| 24 | 
            +
                  inherit_socket: false,
         | 
| 25 | 
            +
                  sentinels: nil,
         | 
| 26 | 
            +
                  role: nil
         | 
| 27 | 
            +
                }.freeze
         | 
| 27 28 |  | 
| 28 29 | 
             
                attr_reader :options
         | 
| 29 30 |  | 
| @@ -89,7 +90,7 @@ class Redis | |
| 89 90 | 
             
                  @pending_reads = 0
         | 
| 90 91 |  | 
| 91 92 | 
             
                  @connector =
         | 
| 92 | 
            -
                    if options. | 
| 93 | 
            +
                    if !@options[:sentinels].nil?
         | 
| 93 94 | 
             
                      Connector::Sentinel.new(@options)
         | 
| 94 95 | 
             
                    elsif options.include?(:connector) && options[:connector].respond_to?(:new)
         | 
| 95 96 | 
             
                      options.delete(:connector).new(@options)
         | 
| @@ -166,6 +167,7 @@ class Redis | |
| 166 167 | 
             
                      end
         | 
| 167 168 | 
             
                    rescue ConnectionError => e
         | 
| 168 169 | 
             
                      return nil if pipeline.shutdown?
         | 
| 170 | 
            +
             | 
| 169 171 | 
             
                      # Assume the pipeline was sent in one piece, but execution of
         | 
| 170 172 | 
             
                      # SHUTDOWN caused none of the replies for commands that were executed
         | 
| 171 173 | 
             
                      # prior to it from coming back around.
         | 
| @@ -244,12 +246,13 @@ class Redis | |
| 244 246 | 
             
                end
         | 
| 245 247 |  | 
| 246 248 | 
             
                def connected?
         | 
| 247 | 
            -
                  !! | 
| 249 | 
            +
                  !!(connection && connection.connected?)
         | 
| 248 250 | 
             
                end
         | 
| 249 251 |  | 
| 250 252 | 
             
                def disconnect
         | 
| 251 253 | 
             
                  connection.disconnect if connected?
         | 
| 252 254 | 
             
                end
         | 
| 255 | 
            +
                alias close disconnect
         | 
| 253 256 |  | 
| 254 257 | 
             
                def reconnect
         | 
| 255 258 | 
             
                  disconnect
         | 
| @@ -300,30 +303,27 @@ class Redis | |
| 300 303 | 
             
                  with_socket_timeout(0, &blk)
         | 
| 301 304 | 
             
                end
         | 
| 302 305 |  | 
| 303 | 
            -
                def with_reconnect(val=true)
         | 
| 304 | 
            -
                   | 
| 305 | 
            -
             | 
| 306 | 
            -
             | 
| 307 | 
            -
                   | 
| 308 | 
            -
                    @reconnect = original
         | 
| 309 | 
            -
                  end
         | 
| 306 | 
            +
                def with_reconnect(val = true)
         | 
| 307 | 
            +
                  original, @reconnect = @reconnect, val
         | 
| 308 | 
            +
                  yield
         | 
| 309 | 
            +
                ensure
         | 
| 310 | 
            +
                  @reconnect = original
         | 
| 310 311 | 
             
                end
         | 
| 311 312 |  | 
| 312 313 | 
             
                def without_reconnect(&blk)
         | 
| 313 314 | 
             
                  with_reconnect(false, &blk)
         | 
| 314 315 | 
             
                end
         | 
| 315 316 |  | 
| 316 | 
            -
             | 
| 317 | 
            +
                protected
         | 
| 317 318 |  | 
| 318 319 | 
             
                def logging(commands)
         | 
| 319 | 
            -
                  return yield unless @logger | 
| 320 | 
            +
                  return yield unless @logger&.debug?
         | 
| 320 321 |  | 
| 321 322 | 
             
                  begin
         | 
| 322 323 | 
             
                    commands.each do |name, *args|
         | 
| 323 324 | 
             
                      logged_args = args.map do |a|
         | 
| 324 | 
            -
                         | 
| 325 | 
            -
                         | 
| 326 | 
            -
                        when a.respond_to?(:to_s)    then a.to_s
         | 
| 325 | 
            +
                        if a.respond_to?(:inspect) then a.inspect
         | 
| 326 | 
            +
                        elsif a.respond_to?(:to_s) then a.to_s
         | 
| 327 327 | 
             
                        else
         | 
| 328 328 | 
             
                          # handle poorly-behaved descendants of BasicObject
         | 
| 329 329 | 
             
                          klass = a.instance_exec { (class << self; self end).superclass }
         | 
| @@ -357,9 +357,9 @@ class Redis | |
| 357 357 | 
             
                       Errno::ENETUNREACH,
         | 
| 358 358 | 
             
                       Errno::ENOENT,
         | 
| 359 359 | 
             
                       Errno::ETIMEDOUT,
         | 
| 360 | 
            -
                       Errno::EINVAL
         | 
| 360 | 
            +
                       Errno::EINVAL => error
         | 
| 361 361 |  | 
| 362 | 
            -
                  raise CannotConnectError, "Error connecting to Redis on #{location} (#{ | 
| 362 | 
            +
                  raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
         | 
| 363 363 | 
             
                end
         | 
| 364 364 |  | 
| 365 365 | 
             
                def ensure_connected
         | 
| @@ -373,9 +373,9 @@ class Redis | |
| 373 373 | 
             
                    if connected?
         | 
| 374 374 | 
             
                      unless inherit_socket? || Process.pid == @pid
         | 
| 375 375 | 
             
                        raise InheritedError,
         | 
| 376 | 
            -
             | 
| 377 | 
            -
             | 
| 378 | 
            -
             | 
| 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."
         | 
| 379 379 | 
             
                      end
         | 
| 380 380 | 
             
                    else
         | 
| 381 381 | 
             
                      connect
         | 
| @@ -386,7 +386,7 @@ class Redis | |
| 386 386 | 
             
                    disconnect
         | 
| 387 387 |  | 
| 388 388 | 
             
                    if attempts <= @options[:reconnect_attempts] && @reconnect
         | 
| 389 | 
            -
                      sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
         | 
| 389 | 
            +
                      sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
         | 
| 390 390 | 
             
                                 @options[:reconnect_delay_max]].min
         | 
| 391 391 |  | 
| 392 392 | 
             
                      Kernel.sleep(sleep_t)
         | 
| @@ -408,16 +408,14 @@ class Redis | |
| 408 408 |  | 
| 409 409 | 
             
                  defaults.keys.each do |key|
         | 
| 410 410 | 
             
                    # Fill in defaults if needed
         | 
| 411 | 
            -
                    if defaults[key].respond_to?(:call)
         | 
| 412 | 
            -
                      defaults[key] = defaults[key].call
         | 
| 413 | 
            -
                    end
         | 
| 411 | 
            +
                    defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
         | 
| 414 412 |  | 
| 415 413 | 
             
                    # Symbolize only keys that are needed
         | 
| 416 | 
            -
                    options[key] = options[key.to_s] if options. | 
| 414 | 
            +
                    options[key] = options[key.to_s] if options.key?(key.to_s)
         | 
| 417 415 | 
             
                  end
         | 
| 418 416 |  | 
| 419 417 | 
             
                  url = options[:url]
         | 
| 420 | 
            -
                  url = defaults[:url] if url | 
| 418 | 
            +
                  url = defaults[:url] if url.nil?
         | 
| 421 419 |  | 
| 422 420 | 
             
                  # Override defaults from URL if given
         | 
| 423 421 | 
             
                  if url
         | 
| @@ -426,7 +424,7 @@ class Redis | |
| 426 424 | 
             
                    uri = URI(url)
         | 
| 427 425 |  | 
| 428 426 | 
             
                    if uri.scheme == "unix"
         | 
| 429 | 
            -
                      defaults[:path] | 
| 427 | 
            +
                      defaults[:path] = uri.path
         | 
| 430 428 | 
             
                    elsif uri.scheme == "redis" || uri.scheme == "rediss"
         | 
| 431 429 | 
             
                      defaults[:scheme]   = uri.scheme
         | 
| 432 430 | 
             
                      defaults[:host]     = uri.host if uri.host
         | 
| @@ -457,7 +455,7 @@ class Redis | |
| 457 455 | 
             
                    options[:port] = options[:port].to_i
         | 
| 458 456 | 
             
                  end
         | 
| 459 457 |  | 
| 460 | 
            -
                  if options. | 
| 458 | 
            +
                  if options.key?(:timeout)
         | 
| 461 459 | 
             
                    options[:connect_timeout] ||= options[:timeout]
         | 
| 462 460 | 
             
                    options[:read_timeout]    ||= options[:timeout]
         | 
| 463 461 | 
             
                    options[:write_timeout]   ||= options[:timeout]
         | 
| @@ -476,7 +474,7 @@ class Redis | |
| 476 474 |  | 
| 477 475 | 
             
                  case options[:tcp_keepalive]
         | 
| 478 476 | 
             
                  when Hash
         | 
| 479 | 
            -
                    [ | 
| 477 | 
            +
                    %i[time intvl probes].each do |key|
         | 
| 480 478 | 
             
                      unless options[:tcp_keepalive][key].is_a?(Integer)
         | 
| 481 479 | 
             
                        raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
         | 
| 482 480 | 
             
                      end
         | 
| @@ -484,13 +482,13 @@ class Redis | |
| 484 482 |  | 
| 485 483 | 
             
                  when Integer
         | 
| 486 484 | 
             
                    if options[:tcp_keepalive] >= 60
         | 
| 487 | 
            -
                      options[:tcp_keepalive] = {: | 
| 485 | 
            +
                      options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
         | 
| 488 486 |  | 
| 489 487 | 
             
                    elsif options[:tcp_keepalive] >= 30
         | 
| 490 | 
            -
                      options[:tcp_keepalive] = {: | 
| 488 | 
            +
                      options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
         | 
| 491 489 |  | 
| 492 490 | 
             
                    elsif options[:tcp_keepalive] >= 5
         | 
| 493 | 
            -
                      options[:tcp_keepalive] = {: | 
| 491 | 
            +
                      options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
         | 
| 494 492 | 
             
                    end
         | 
| 495 493 | 
             
                  end
         | 
| 496 494 |  | 
| @@ -502,14 +500,14 @@ class Redis | |
| 502 500 | 
             
                def _parse_driver(driver)
         | 
| 503 501 | 
             
                  driver = driver.to_s if driver.is_a?(Symbol)
         | 
| 504 502 |  | 
| 505 | 
            -
                  if driver. | 
| 503 | 
            +
                  if driver.is_a?(String)
         | 
| 506 504 | 
             
                    begin
         | 
| 507 505 | 
             
                      require_relative "connection/#{driver}"
         | 
| 508 | 
            -
                    rescue LoadError, NameError | 
| 506 | 
            +
                    rescue LoadError, NameError
         | 
| 509 507 | 
             
                      begin
         | 
| 510 508 | 
             
                        require "connection/#{driver}"
         | 
| 511 | 
            -
                      rescue LoadError, NameError =>  | 
| 512 | 
            -
                        raise  | 
| 509 | 
            +
                      rescue LoadError, NameError => error
         | 
| 510 | 
            +
                        raise "Cannot load driver #{driver.inspect}: #{error.message}"
         | 
| 513 511 | 
             
                      end
         | 
| 514 512 | 
             
                    end
         | 
| 515 513 |  | 
| @@ -528,8 +526,7 @@ class Redis | |
| 528 526 | 
             
                    @options
         | 
| 529 527 | 
             
                  end
         | 
| 530 528 |  | 
| 531 | 
            -
                  def check(client)
         | 
| 532 | 
            -
                  end
         | 
| 529 | 
            +
                  def check(client); end
         | 
| 533 530 |  | 
| 534 531 | 
             
                  class Sentinel < Connector
         | 
| 535 532 | 
             
                    def initialize(options)
         | 
| @@ -538,7 +535,7 @@ class Redis | |
| 538 535 | 
             
                      @options[:db] = DEFAULTS.fetch(:db)
         | 
| 539 536 |  | 
| 540 537 | 
             
                      @sentinels = @options.delete(:sentinels).dup
         | 
| 541 | 
            -
                      @role = @options | 
| 538 | 
            +
                      @role = (@options[:role] || "master").to_s
         | 
| 542 539 | 
             
                      @master = @options[:host]
         | 
| 543 540 | 
             
                    end
         | 
| 544 541 |  | 
| @@ -561,13 +558,13 @@ class Redis | |
| 561 558 |  | 
| 562 559 | 
             
                    def resolve
         | 
| 563 560 | 
             
                      result = case @role
         | 
| 564 | 
            -
             | 
| 565 | 
            -
             | 
| 566 | 
            -
             | 
| 567 | 
            -
             | 
| 568 | 
            -
             | 
| 569 | 
            -
             | 
| 570 | 
            -
             | 
| 561 | 
            +
                      when "master"
         | 
| 562 | 
            +
                        resolve_master
         | 
| 563 | 
            +
                      when "slave"
         | 
| 564 | 
            +
                        resolve_slave
         | 
| 565 | 
            +
                      else
         | 
| 566 | 
            +
                        raise ArgumentError, "Unknown instance role #{@role}"
         | 
| 567 | 
            +
                      end
         | 
| 571 568 |  | 
| 572 569 | 
             
                      result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
         | 
| 573 570 | 
             
                    end
         | 
| @@ -575,11 +572,11 @@ class Redis | |
| 575 572 | 
             
                    def sentinel_detect
         | 
| 576 573 | 
             
                      @sentinels.each do |sentinel|
         | 
| 577 574 | 
             
                        client = Client.new(@options.merge({
         | 
| 578 | 
            -
             | 
| 579 | 
            -
             | 
| 580 | 
            -
             | 
| 581 | 
            -
             | 
| 582 | 
            -
             | 
| 575 | 
            +
                                                             host: sentinel[:host] || sentinel["host"],
         | 
| 576 | 
            +
                                                             port: sentinel[:port] || sentinel["port"],
         | 
| 577 | 
            +
                                                             password: sentinel[:password] || sentinel["password"],
         | 
| 578 | 
            +
                                                             reconnect_attempts: 0
         | 
| 579 | 
            +
                                                           }))
         | 
| 583 580 |  | 
| 584 581 | 
             
                        begin
         | 
| 585 582 | 
             
                          if result = yield(client)
         | 
| @@ -601,7 +598,7 @@ class Redis | |
| 601 598 | 
             
                    def resolve_master
         | 
| 602 599 | 
             
                      sentinel_detect do |client|
         | 
| 603 600 | 
             
                        if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
         | 
| 604 | 
            -
                          {: | 
| 601 | 
            +
                          { host: reply[0], port: reply[1] }
         | 
| 605 602 | 
             
                        end
         | 
| 606 603 | 
             
                      end
         | 
| 607 604 | 
             
                    end
         | 
| @@ -619,7 +616,7 @@ class Redis | |
| 619 616 | 
             
                            slave = slaves.sample
         | 
| 620 617 | 
             
                            {
         | 
| 621 618 | 
             
                              host: slave.fetch('ip'),
         | 
| 622 | 
            -
                              port: slave.fetch('port') | 
| 619 | 
            +
                              port: slave.fetch('port')
         | 
| 623 620 | 
             
                            }
         | 
| 624 621 | 
             
                          end
         | 
| 625 622 | 
             
                        end
         | 
    
        data/lib/redis/cluster.rb
    CHANGED
    
    | @@ -80,6 +80,7 @@ class Redis | |
| 80 80 | 
             
                def call_pipeline(pipeline)
         | 
| 81 81 | 
             
                  node_keys, command_keys = extract_keys_in_pipeline(pipeline)
         | 
| 82 82 | 
             
                  raise CrossSlotPipeliningError, command_keys if node_keys.size > 1
         | 
| 83 | 
            +
             | 
| 83 84 | 
             
                  node = find_node(node_keys.first)
         | 
| 84 85 | 
             
                  try_send(node, :call_pipeline, pipeline)
         | 
| 85 86 | 
             
                end
         | 
| @@ -112,12 +113,11 @@ class Redis | |
| 112 113 | 
             
                  node = Node.new(option.per_node_key)
         | 
| 113 114 | 
             
                  available_slots = SlotLoader.load(node)
         | 
| 114 115 | 
             
                  node_flags = NodeLoader.load_flags(node)
         | 
| 115 | 
            -
                   | 
| 116 | 
            -
                  option.update_node(available_node_urls)
         | 
| 116 | 
            +
                  option.update_node(available_slots.keys.map { |k| NodeKey.optionize(k) })
         | 
| 117 117 | 
             
                  [Node.new(option.per_node_key, node_flags, option.use_replica?),
         | 
| 118 118 | 
             
                   Slot.new(available_slots, node_flags, option.use_replica?)]
         | 
| 119 119 | 
             
                ensure
         | 
| 120 | 
            -
                  node | 
| 120 | 
            +
                  node&.each(&:disconnect)
         | 
| 121 121 | 
             
                end
         | 
| 122 122 |  | 
| 123 123 | 
             
                def fetch_command_details(nodes)
         | 
| @@ -216,9 +216,14 @@ class Redis | |
| 216 216 | 
             
                  node.public_send(method_name, *args, &block)
         | 
| 217 217 | 
             
                rescue CommandError => err
         | 
| 218 218 | 
             
                  if err.message.start_with?('MOVED')
         | 
| 219 | 
            -
                     | 
| 219 | 
            +
                    raise if retry_count <= 0
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                    node = assign_redirection_node(err.message)
         | 
| 222 | 
            +
                    retry_count -= 1
         | 
| 223 | 
            +
                    retry
         | 
| 220 224 | 
             
                  elsif err.message.start_with?('ASK')
         | 
| 221 225 | 
             
                    raise if retry_count <= 0
         | 
| 226 | 
            +
             | 
| 222 227 | 
             
                    node = assign_asking_node(err.message)
         | 
| 223 228 | 
             
                    node.call(%i[asking])
         | 
| 224 229 | 
             
                    retry_count -= 1
         | 
| @@ -226,6 +231,9 @@ class Redis | |
| 226 231 | 
             
                  else
         | 
| 227 232 | 
             
                    raise
         | 
| 228 233 | 
             
                  end
         | 
| 234 | 
            +
                rescue CannotConnectError
         | 
| 235 | 
            +
                  update_cluster_info!
         | 
| 236 | 
            +
                  raise
         | 
| 229 237 | 
             
                end
         | 
| 230 238 |  | 
| 231 239 | 
             
                def assign_redirection_node(err_msg)
         | 
| @@ -261,6 +269,7 @@ class Redis | |
| 261 269 |  | 
| 262 270 | 
             
                def find_node(node_key)
         | 
| 263 271 | 
             
                  return @node.sample if node_key.nil?
         | 
| 272 | 
            +
             | 
| 264 273 | 
             
                  @node.find_by(node_key)
         | 
| 265 274 | 
             
                rescue Node::ReloadNeeded
         | 
| 266 275 | 
             
                  update_cluster_info!(node_key)
         | 
    
        data/lib/redis/cluster/node.rb
    CHANGED
    
    | @@ -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  | 
| 16 | 
            -
                     | 
| 17 | 
            -
                     | 
| 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)
         | 
    
        data/lib/redis/cluster/option.rb
    CHANGED
    
    | @@ -15,36 +15,35 @@ class Redis | |
| 15 15 | 
             
                  def initialize(options)
         | 
| 16 16 | 
             
                    options = options.dup
         | 
| 17 17 | 
             
                    node_addrs = options.delete(:cluster)
         | 
| 18 | 
            -
                    @ | 
| 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 | 
            -
                    @ | 
| 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 | 
            -
                    @ | 
| 35 | 
            +
                    @node_opts = build_node_options(addrs)
         | 
| 38 36 | 
             
                  end
         | 
| 39 37 |  | 
| 40 38 | 
             
                  def add_node(host, port)
         | 
| 41 | 
            -
                    @ | 
| 39 | 
            +
                    @node_opts << { host: host, port: port }
         | 
| 42 40 | 
             
                  end
         | 
| 43 41 |  | 
| 44 42 | 
             
                  private
         | 
| 45 43 |  | 
| 46 | 
            -
                  def  | 
| 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 | 
            -
                       | 
| 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 | 
            -
             | 
| 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  | 
| 71 | 
            +
                  def parse_node_option(addr)
         | 
| 71 72 | 
             
                    addr = addr.map { |k, v| [k.to_sym, v] }.to_h
         | 
| 72 | 
            -
                     | 
| 73 | 
            -
             | 
| 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
         |