redis 4.5.0 → 4.7.0
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 +65 -0
 - data/README.md +25 -10
 - data/lib/redis/client.rb +23 -12
 - data/lib/redis/cluster/command.rb +4 -6
 - data/lib/redis/cluster/command_loader.rb +5 -5
 - data/lib/redis/cluster/node.rb +12 -0
 - data/lib/redis/cluster/node_loader.rb +8 -11
 - data/lib/redis/cluster/option.rb +10 -3
 - data/lib/redis/cluster/slot_loader.rb +9 -12
 - data/lib/redis/cluster.rb +24 -0
 - data/lib/redis/commands/bitmaps.rb +63 -0
 - data/lib/redis/commands/cluster.rb +45 -0
 - data/lib/redis/commands/connection.rb +58 -0
 - data/lib/redis/commands/geo.rb +84 -0
 - data/lib/redis/commands/hashes.rb +251 -0
 - data/lib/redis/commands/hyper_log_log.rb +37 -0
 - data/lib/redis/commands/keys.rb +411 -0
 - data/lib/redis/commands/lists.rb +289 -0
 - data/lib/redis/commands/pubsub.rb +72 -0
 - data/lib/redis/commands/scripting.rb +114 -0
 - data/lib/redis/commands/server.rb +188 -0
 - data/lib/redis/commands/sets.rb +207 -0
 - data/lib/redis/commands/sorted_sets.rb +812 -0
 - data/lib/redis/commands/streams.rb +382 -0
 - data/lib/redis/commands/strings.rb +313 -0
 - data/lib/redis/commands/transactions.rb +139 -0
 - data/lib/redis/commands.rb +242 -0
 - data/lib/redis/connection/hiredis.rb +3 -4
 - data/lib/redis/connection/ruby.rb +14 -28
 - data/lib/redis/connection/synchrony.rb +10 -8
 - data/lib/redis/connection.rb +1 -1
 - data/lib/redis/distributed.rb +46 -9
 - data/lib/redis/errors.rb +9 -0
 - data/lib/redis/pipeline.rb +128 -3
 - data/lib/redis/version.rb +1 -1
 - data/lib/redis.rb +137 -3673
 - metadata +21 -4
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 5236e71d674779c964e99ebf2eac29efe80339a93de6907acfcd230a5d312153
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 208b4bf708d70a570c354da2d5570972416cbe6007e1f8d99c31f3ef4958a342
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: c9c6446c4b666435afcc66256a4387b0360da61cfbf941252d91561e893219d650a8cd1bf98e8a28825c258e543078e6d3b6e22cb790e2ac9ff42af915dd8ef0
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: e6649016acce9f6fd2e1767840ac3c07f5fc825576e5c304b4f1cb0b7a2e1c8ce8043450de75a1c910098a47f5bf53230b4efd74b7590b17f5022b04f0e23583
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,5 +1,70 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # Unreleased
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            # 4.7.0
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            * Support single endpoint architecture with SSL/TLS in cluster mode. See #1086.
         
     | 
| 
      
 6 
     | 
    
         
            +
            * `zrem` and `zadd` act as noop when provided an empty list of keys. See #1097.
         
     | 
| 
      
 7 
     | 
    
         
            +
            * Support IPv6 URLs.
         
     | 
| 
      
 8 
     | 
    
         
            +
            * Add `Redis#with` for better compatibility with `connection_pool` usage.
         
     | 
| 
      
 9 
     | 
    
         
            +
            * Fix the block form of `multi` called inside `pipelined`. Previously the `MUTLI/EXEC` wouldn't be sent. See #1073.
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            # 4.6.0
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            * Deprecate `Redis.current`.
         
     | 
| 
      
 14 
     | 
    
         
            +
            * Deprecate calling commands on `Redis` inside `Redis#pipelined`. See #1059.
         
     | 
| 
      
 15 
     | 
    
         
            +
              ```ruby
         
     | 
| 
      
 16 
     | 
    
         
            +
              redis.pipelined do
         
     | 
| 
      
 17 
     | 
    
         
            +
                redis.get("key")
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
              ```
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              should be replaced by:
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              ```ruby
         
     | 
| 
      
 24 
     | 
    
         
            +
              redis.pipelined do |pipeline|
         
     | 
| 
      
 25 
     | 
    
         
            +
                pipeline.get("key")
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
              ```
         
     | 
| 
      
 28 
     | 
    
         
            +
            * Deprecate calling commands on `Redis` inside `Redis#multi`. See #1059.
         
     | 
| 
      
 29 
     | 
    
         
            +
              ```ruby
         
     | 
| 
      
 30 
     | 
    
         
            +
              redis.multi do
         
     | 
| 
      
 31 
     | 
    
         
            +
                redis.get("key")
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
              ```
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              should be replaced by:
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              ```ruby
         
     | 
| 
      
 38 
     | 
    
         
            +
              redis.multi do |transaction|
         
     | 
| 
      
 39 
     | 
    
         
            +
                transaction.get("key")
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
              ```
         
     | 
| 
      
 42 
     | 
    
         
            +
            * Deprecate `Redis#queue` and `Redis#commit`. See #1059.
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            * Fix `zpopmax` and `zpopmin` when called inside a pipeline. See #1055.
         
     | 
| 
      
 45 
     | 
    
         
            +
            * `Redis#synchronize` is now private like it should always have been.
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
            * Add `Redis.silence_deprecations=` to turn off deprecation warnings.
         
     | 
| 
      
 48 
     | 
    
         
            +
              If you don't wish to see warnings yet, you can set `Redis.silence_deprecations = true`.
         
     | 
| 
      
 49 
     | 
    
         
            +
              It is however heavily recommended to fix them instead when possible.
         
     | 
| 
      
 50 
     | 
    
         
            +
            * Add `Redis.raise_deprecations=` to turn deprecation warnings into errors.
         
     | 
| 
      
 51 
     | 
    
         
            +
              This makes it easier to identitify the source of deprecated APIs usage.
         
     | 
| 
      
 52 
     | 
    
         
            +
              It is recommended to set `Redis.raise_deprecations = true` in development and test environments.
         
     | 
| 
      
 53 
     | 
    
         
            +
            * Add new options to ZRANGE. See #1053.
         
     | 
| 
      
 54 
     | 
    
         
            +
            * Add ZRANGESTORE command. See #1053.
         
     | 
| 
      
 55 
     | 
    
         
            +
            * Add SCAN support for `Redis::Cluster`. See #1049.
         
     | 
| 
      
 56 
     | 
    
         
            +
            * Add COPY command. See #1053. See #1048.
         
     | 
| 
      
 57 
     | 
    
         
            +
            * Add ZDIFFSTORE command. See #1046.
         
     | 
| 
      
 58 
     | 
    
         
            +
            * Add ZDIFF command. See #1044.
         
     | 
| 
      
 59 
     | 
    
         
            +
            * Add ZUNION command. See #1042.
         
     | 
| 
      
 60 
     | 
    
         
            +
            * Add HRANDFIELD command. See #1040.
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
            # 4.5.1
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
            * Restore the accidential auth behavior of redis-rb 4.3.0 with a warning. If provided with the `default` user's password, but a wrong username,
         
     | 
| 
      
 65 
     | 
    
         
            +
              redis-rb will first try to connect as the provided user, but then will fallback to connect as the `default` user with the provided password.
         
     | 
| 
      
 66 
     | 
    
         
            +
              This behavior is deprecated and will be removed in Redis 4.6.0. Fix #1038.
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
       3 
68 
     | 
    
         
             
            # 4.5.0
         
     | 
| 
       4 
69 
     | 
    
         | 
| 
       5 
70 
     | 
    
         
             
            * Handle parts of the command using incompatible encodings. See #1037.
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -155,6 +155,21 @@ redis.mget('{key}1', '{key}2') 
     | 
|
| 
       155 
155 
     | 
    
         
             
            * The client support permanent node failures, and will reroute requests to promoted slaves.
         
     | 
| 
       156 
156 
     | 
    
         
             
            * The client supports `MOVED` and `ASK` redirections transparently.
         
     | 
| 
       157 
157 
     | 
    
         | 
| 
      
 158 
     | 
    
         
            +
            ## Cluster mode with SSL/TLS
         
     | 
| 
      
 159 
     | 
    
         
            +
            Since Redis can return FQDN of nodes in reply to client since `7.*` with CLUSTER commands, we can use cluster feature with SSL/TLS connection like this:
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 162 
     | 
    
         
            +
            Redis.new(cluster: %w[rediss://foo.example.com:6379])
         
     | 
| 
      
 163 
     | 
    
         
            +
            ```
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
            On the other hand, in Redis versions prior to `6.*`, you can specify options like the following if cluster mode is enabled and client has to connect to nodes via single endpoint with SSL/TLS.
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 168 
     | 
    
         
            +
            Redis.new(cluster: %w[rediss://foo-endpoint.example.com:6379], fixed_hostname: 'foo-endpoint.example.com')
         
     | 
| 
      
 169 
     | 
    
         
            +
            ```
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
            In case of the above architecture, if you don't pass the `fixed_hostname` option to the client and servers return IP addresses of nodes, the client may fail to verify certificates.
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
       158 
173 
     | 
    
         
             
            ## Storing objects
         
     | 
| 
       159 
174 
     | 
    
         | 
| 
       160 
175 
     | 
    
         
             
            Redis "string" types can be used to store serialized Ruby objects, for
         
     | 
| 
         @@ -184,9 +199,9 @@ commands to Redis and gathers their replies. These replies are returned 
     | 
|
| 
       184 
199 
     | 
    
         
             
            by the `#pipelined` method.
         
     | 
| 
       185 
200 
     | 
    
         | 
| 
       186 
201 
     | 
    
         
             
            ```ruby
         
     | 
| 
       187 
     | 
    
         
            -
            redis.pipelined do
         
     | 
| 
       188 
     | 
    
         
            -
               
     | 
| 
       189 
     | 
    
         
            -
               
     | 
| 
      
 202 
     | 
    
         
            +
            redis.pipelined do |pipeline|
         
     | 
| 
      
 203 
     | 
    
         
            +
              pipeline.set "foo", "bar"
         
     | 
| 
      
 204 
     | 
    
         
            +
              pipeline.incr "baz"
         
     | 
| 
       190 
205 
     | 
    
         
             
            end
         
     | 
| 
       191 
206 
     | 
    
         
             
            # => ["OK", 1]
         
     | 
| 
       192 
207 
     | 
    
         
             
            ```
         
     | 
| 
         @@ -200,9 +215,9 @@ the regular pipeline, the replies to the commands are returned by the 
     | 
|
| 
       200 
215 
     | 
    
         
             
            `#multi` method.
         
     | 
| 
       201 
216 
     | 
    
         | 
| 
       202 
217 
     | 
    
         
             
            ```ruby
         
     | 
| 
       203 
     | 
    
         
            -
            redis.multi do
         
     | 
| 
       204 
     | 
    
         
            -
               
     | 
| 
       205 
     | 
    
         
            -
               
     | 
| 
      
 218 
     | 
    
         
            +
            redis.multi do |transaction|
         
     | 
| 
      
 219 
     | 
    
         
            +
              transaction.set "foo", "bar"
         
     | 
| 
      
 220 
     | 
    
         
            +
              transaction.incr "baz"
         
     | 
| 
       206 
221 
     | 
    
         
             
            end
         
     | 
| 
       207 
222 
     | 
    
         
             
            # => ["OK", 1]
         
     | 
| 
       208 
223 
     | 
    
         
             
            ```
         
     | 
| 
         @@ -210,15 +225,15 @@ end 
     | 
|
| 
       210 
225 
     | 
    
         
             
            ### Futures
         
     | 
| 
       211 
226 
     | 
    
         | 
| 
       212 
227 
     | 
    
         
             
            Replies to commands in a pipeline can be accessed via the *futures* they
         
     | 
| 
       213 
     | 
    
         
            -
            emit (since redis-rb 3.0). All calls  
     | 
| 
      
 228 
     | 
    
         
            +
            emit (since redis-rb 3.0). All calls on the pipeline object return a
         
     | 
| 
       214 
229 
     | 
    
         
             
            `Future` object, which responds to the `#value` method. When the
         
     | 
| 
       215 
230 
     | 
    
         
             
            pipeline has successfully executed, all futures are assigned their
         
     | 
| 
       216 
231 
     | 
    
         
             
            respective replies and can be used.
         
     | 
| 
       217 
232 
     | 
    
         | 
| 
       218 
233 
     | 
    
         
             
            ```ruby
         
     | 
| 
       219 
     | 
    
         
            -
            redis.pipelined do
         
     | 
| 
       220 
     | 
    
         
            -
              @set =  
     | 
| 
       221 
     | 
    
         
            -
              @incr =  
     | 
| 
      
 234 
     | 
    
         
            +
            redis.pipelined do |pipeline|
         
     | 
| 
      
 235 
     | 
    
         
            +
              @set = pipeline.set "foo", "bar"
         
     | 
| 
      
 236 
     | 
    
         
            +
              @incr = pipeline.incr "baz"
         
     | 
| 
       222 
237 
     | 
    
         
             
            end
         
     | 
| 
       223 
238 
     | 
    
         | 
| 
       224 
239 
     | 
    
         
             
            @set.value
         
     | 
    
        data/lib/redis/client.rb
    CHANGED
    
    | 
         @@ -1,8 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require_relative "errors"
         
     | 
| 
       4 
3 
     | 
    
         
             
            require "socket"
         
     | 
| 
       5 
4 
     | 
    
         
             
            require "cgi"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "redis/errors"
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
            class Redis
         
     | 
| 
       8 
8 
     | 
    
         
             
              class Client
         
     | 
| 
         @@ -32,7 +32,7 @@ class Redis 
     | 
|
| 
       32 
32 
     | 
    
         
             
                  role: nil
         
     | 
| 
       33 
33 
     | 
    
         
             
                }.freeze
         
     | 
| 
       34 
34 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                attr_reader :options
         
     | 
| 
      
 35 
     | 
    
         
            +
                attr_reader :options, :connection, :command_map
         
     | 
| 
       36 
36 
     | 
    
         | 
| 
       37 
37 
     | 
    
         
             
                def scheme
         
     | 
| 
       38 
38 
     | 
    
         
             
                  @options[:scheme]
         
     | 
| 
         @@ -87,8 +87,6 @@ class Redis 
     | 
|
| 
       87 
87 
     | 
    
         
             
                end
         
     | 
| 
       88 
88 
     | 
    
         | 
| 
       89 
89 
     | 
    
         
             
                attr_accessor :logger
         
     | 
| 
       90 
     | 
    
         
            -
                attr_reader :connection
         
     | 
| 
       91 
     | 
    
         
            -
                attr_reader :command_map
         
     | 
| 
       92 
90 
     | 
    
         | 
| 
       93 
91 
     | 
    
         
             
                def initialize(options = {})
         
     | 
| 
       94 
92 
     | 
    
         
             
                  @options = _parse_options(options)
         
     | 
| 
         @@ -120,8 +118,19 @@ class Redis 
     | 
|
| 
       120 
118 
     | 
    
         
             
                        begin
         
     | 
| 
       121 
119 
     | 
    
         
             
                          call [:auth, username, password]
         
     | 
| 
       122 
120 
     | 
    
         
             
                        rescue CommandError => err # Likely on Redis < 6
         
     | 
| 
       123 
     | 
    
         
            -
                           
     | 
| 
      
 121 
     | 
    
         
            +
                          case err.message
         
     | 
| 
      
 122 
     | 
    
         
            +
                          when /ERR wrong number of arguments for 'auth' command/
         
     | 
| 
       124 
123 
     | 
    
         
             
                            call [:auth, password]
         
     | 
| 
      
 124 
     | 
    
         
            +
                          when /WRONGPASS invalid username-password pair/
         
     | 
| 
      
 125 
     | 
    
         
            +
                            begin
         
     | 
| 
      
 126 
     | 
    
         
            +
                              call [:auth, password]
         
     | 
| 
      
 127 
     | 
    
         
            +
                            rescue CommandError
         
     | 
| 
      
 128 
     | 
    
         
            +
                              raise err
         
     | 
| 
      
 129 
     | 
    
         
            +
                            end
         
     | 
| 
      
 130 
     | 
    
         
            +
                            ::Redis.deprecate!(
         
     | 
| 
      
 131 
     | 
    
         
            +
                              "[redis-rb] The Redis connection was configured with username #{username.inspect}, but" \
         
     | 
| 
      
 132 
     | 
    
         
            +
                              " the provided password was for the default user. This will start failing in redis-rb 5.0.0."
         
     | 
| 
      
 133 
     | 
    
         
            +
                            )
         
     | 
| 
       125 
134 
     | 
    
         
             
                          else
         
     | 
| 
       126 
135 
     | 
    
         
             
                            raise
         
     | 
| 
       127 
136 
     | 
    
         
             
                          end
         
     | 
| 
         @@ -141,7 +150,7 @@ class Redis 
     | 
|
| 
       141 
150 
     | 
    
         
             
                end
         
     | 
| 
       142 
151 
     | 
    
         | 
| 
       143 
152 
     | 
    
         
             
                def id
         
     | 
| 
       144 
     | 
    
         
            -
                  @options[:id] || " 
     | 
| 
      
 153 
     | 
    
         
            +
                  @options[:id] || "#{@options[:ssl] ? 'rediss' : @options[:scheme]}://#{location}/#{db}"
         
     | 
| 
       145 
154 
     | 
    
         
             
                end
         
     | 
| 
       146 
155 
     | 
    
         | 
| 
       147 
156 
     | 
    
         
             
                def location
         
     | 
| 
         @@ -242,7 +251,8 @@ class Redis 
     | 
|
| 
       242 
251 
     | 
    
         
             
                  result
         
     | 
| 
       243 
252 
     | 
    
         
             
                end
         
     | 
| 
       244 
253 
     | 
    
         | 
| 
       245 
     | 
    
         
            -
                def call_with_timeout(command,  
     | 
| 
      
 254 
     | 
    
         
            +
                def call_with_timeout(command, extra_timeout, &blk)
         
     | 
| 
      
 255 
     | 
    
         
            +
                  timeout = extra_timeout == 0 ? 0 : self.timeout + extra_timeout
         
     | 
| 
       246 
256 
     | 
    
         
             
                  with_socket_timeout(timeout) do
         
     | 
| 
       247 
257 
     | 
    
         
             
                    call(command, &blk)
         
     | 
| 
       248 
258 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -432,7 +442,7 @@ class Redis 
     | 
|
| 
       432 
442 
     | 
    
         
             
                  defaults = DEFAULTS.dup
         
     | 
| 
       433 
443 
     | 
    
         
             
                  options = options.dup
         
     | 
| 
       434 
444 
     | 
    
         | 
| 
       435 
     | 
    
         
            -
                  defaults. 
     | 
| 
      
 445 
     | 
    
         
            +
                  defaults.each_key do |key|
         
     | 
| 
       436 
446 
     | 
    
         
             
                    # Fill in defaults if needed
         
     | 
| 
       437 
447 
     | 
    
         
             
                    defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
         
     | 
| 
       438 
448 
     | 
    
         | 
| 
         @@ -449,11 +459,12 @@ class Redis 
     | 
|
| 
       449 
459 
     | 
    
         | 
| 
       450 
460 
     | 
    
         
             
                    uri = URI(url)
         
     | 
| 
       451 
461 
     | 
    
         | 
| 
       452 
     | 
    
         
            -
                     
     | 
| 
      
 462 
     | 
    
         
            +
                    case uri.scheme
         
     | 
| 
      
 463 
     | 
    
         
            +
                    when "unix"
         
     | 
| 
       453 
464 
     | 
    
         
             
                      defaults[:path] = uri.path
         
     | 
| 
       454 
     | 
    
         
            -
                     
     | 
| 
      
 465 
     | 
    
         
            +
                    when "redis", "rediss"
         
     | 
| 
       455 
466 
     | 
    
         
             
                      defaults[:scheme]   = uri.scheme
         
     | 
| 
       456 
     | 
    
         
            -
                      defaults[:host]     = uri.host if uri.host
         
     | 
| 
      
 467 
     | 
    
         
            +
                      defaults[:host]     = uri.host.sub(/\A\[(.*)\]\z/, '\1') if uri.host
         
     | 
| 
       457 
468 
     | 
    
         
             
                      defaults[:port]     = uri.port if uri.port
         
     | 
| 
       458 
469 
     | 
    
         
             
                      defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
         
     | 
| 
       459 
470 
     | 
    
         
             
                      defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
         
     | 
| 
         @@ -467,7 +478,7 @@ class Redis 
     | 
|
| 
       467 
478 
     | 
    
         
             
                  end
         
     | 
| 
       468 
479 
     | 
    
         | 
| 
       469 
480 
     | 
    
         
             
                  # Use default when option is not specified or nil
         
     | 
| 
       470 
     | 
    
         
            -
                  defaults. 
     | 
| 
      
 481 
     | 
    
         
            +
                  defaults.each_key do |key|
         
     | 
| 
       471 
482 
     | 
    
         
             
                    options[key] = defaults[key] if options[key].nil?
         
     | 
| 
       472 
483 
     | 
    
         
             
                  end
         
     | 
| 
       473 
484 
     | 
    
         | 
| 
         @@ -31,13 +31,13 @@ class Redis 
     | 
|
| 
       31 
31 
     | 
    
         
             
                  private
         
     | 
| 
       32 
32 
     | 
    
         | 
| 
       33 
33 
     | 
    
         
             
                  def pick_details(details)
         
     | 
| 
       34 
     | 
    
         
            -
                    details. 
     | 
| 
       35 
     | 
    
         
            -
                       
     | 
| 
      
 34 
     | 
    
         
            +
                    details.transform_values do |detail|
         
     | 
| 
      
 35 
     | 
    
         
            +
                      {
         
     | 
| 
       36 
36 
     | 
    
         
             
                        first_key_position: detail[:first],
         
     | 
| 
       37 
37 
     | 
    
         
             
                        write: detail[:flags].include?('write'),
         
     | 
| 
       38 
38 
     | 
    
         
             
                        readonly: detail[:flags].include?('readonly')
         
     | 
| 
       39 
     | 
    
         
            -
                      } 
     | 
| 
       40 
     | 
    
         
            -
                    end 
     | 
| 
      
 39 
     | 
    
         
            +
                      }
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
       41 
41 
     | 
    
         
             
                  end
         
     | 
| 
       42 
42 
     | 
    
         | 
| 
       43 
43 
     | 
    
         
             
                  def dig_details(command, key)
         
     | 
| 
         @@ -53,8 +53,6 @@ class Redis 
     | 
|
| 
       53 
53 
     | 
    
         
             
                    when 'object' then 2
         
     | 
| 
       54 
54 
     | 
    
         
             
                    when 'memory'
         
     | 
| 
       55 
55 
     | 
    
         
             
                      command[1].to_s.casecmp('usage').zero? ? 2 : 0
         
     | 
| 
       56 
     | 
    
         
            -
                    when 'scan', 'sscan', 'hscan', 'zscan'
         
     | 
| 
       57 
     | 
    
         
            -
                      determine_optional_key_position(command, 'match')
         
     | 
| 
       58 
56 
     | 
    
         
             
                    when 'xread', 'xreadgroup'
         
     | 
| 
       59 
57 
     | 
    
         
             
                      determine_optional_key_position(command, 'streams')
         
     | 
| 
       60 
58 
     | 
    
         
             
                    else
         
     | 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'redis/errors'
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            class Redis
         
     | 
| 
       6 
6 
     | 
    
         
             
              class Cluster
         
     | 
| 
         @@ -10,15 +10,15 @@ class Redis 
     | 
|
| 
       10 
10 
     | 
    
         
             
                  module_function
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
                  def load(nodes)
         
     | 
| 
       13 
     | 
    
         
            -
                    nodes. 
     | 
| 
      
 13 
     | 
    
         
            +
                    errors = nodes.map do |node|
         
     | 
| 
       14 
14 
     | 
    
         
             
                      begin
         
     | 
| 
       15 
15 
     | 
    
         
             
                        return fetch_command_details(node)
         
     | 
| 
       16 
     | 
    
         
            -
                      rescue CannotConnectError, ConnectionError, CommandError
         
     | 
| 
       17 
     | 
    
         
            -
                         
     | 
| 
      
 16 
     | 
    
         
            +
                      rescue CannotConnectError, ConnectionError, CommandError => error
         
     | 
| 
      
 17 
     | 
    
         
            +
                        error
         
     | 
| 
       18 
18 
     | 
    
         
             
                      end
         
     | 
| 
       19 
19 
     | 
    
         
             
                    end
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                    raise  
     | 
| 
      
 21 
     | 
    
         
            +
                    raise InitialSetupError, errors
         
     | 
| 
       22 
22 
     | 
    
         
             
                  end
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
24 
     | 
    
         
             
                  def fetch_command_details(node)
         
     | 
    
        data/lib/redis/cluster/node.rb
    CHANGED
    
    | 
         @@ -58,6 +58,18 @@ class Redis 
     | 
|
| 
       58 
58 
     | 
    
         
             
                    try_map { |_, client| client.process(commands, &block) }.values
         
     | 
| 
       59 
59 
     | 
    
         
             
                  end
         
     | 
| 
       60 
60 
     | 
    
         | 
| 
      
 61 
     | 
    
         
            +
                  def scale_reading_clients
         
     | 
| 
      
 62 
     | 
    
         
            +
                    reading_clients = []
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    @clients.each do |node_key, client|
         
     | 
| 
      
 65 
     | 
    
         
            +
                      next unless replica_disabled? ? master?(node_key) : slave?(node_key)
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                      reading_clients << client
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                    reading_clients
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
       61 
73 
     | 
    
         
             
                  private
         
     | 
| 
       62 
74 
     | 
    
         | 
| 
       63 
75 
     | 
    
         
             
                  def replica_disabled?
         
     | 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'redis/errors'
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            class Redis
         
     | 
| 
       6 
6 
     | 
    
         
             
              class Cluster
         
     | 
| 
         @@ -9,16 +9,15 @@ class Redis 
     | 
|
| 
       9 
9 
     | 
    
         
             
                  module_function
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
                  def load_flags(nodes)
         
     | 
| 
       12 
     | 
    
         
            -
                     
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
                       
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
      
 12 
     | 
    
         
            +
                    errors = nodes.map do |node|
         
     | 
| 
      
 13 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 14 
     | 
    
         
            +
                        return fetch_node_info(node)
         
     | 
| 
      
 15 
     | 
    
         
            +
                      rescue CannotConnectError, ConnectionError, CommandError => error
         
     | 
| 
      
 16 
     | 
    
         
            +
                        error
         
     | 
| 
      
 17 
     | 
    
         
            +
                      end
         
     | 
| 
       17 
18 
     | 
    
         
             
                    end
         
     | 
| 
       18 
19 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
                     
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                    raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
         
     | 
| 
      
 20 
     | 
    
         
            +
                    raise InitialSetupError, errors
         
     | 
| 
       22 
21 
     | 
    
         
             
                  end
         
     | 
| 
       23 
22 
     | 
    
         | 
| 
       24 
23 
     | 
    
         
             
                  def fetch_node_info(node)
         
     | 
| 
         @@ -27,8 +26,6 @@ class Redis 
     | 
|
| 
       27 
26 
     | 
    
         
             
                        .map { |str| str.split(' ') }
         
     | 
| 
       28 
27 
     | 
    
         
             
                        .map { |arr| [arr[1].split('@').first, (arr[2].split(',') & %w[master slave]).first] }
         
     | 
| 
       29 
28 
     | 
    
         
             
                        .to_h
         
     | 
| 
       30 
     | 
    
         
            -
                  rescue CannotConnectError, ConnectionError, CommandError
         
     | 
| 
       31 
     | 
    
         
            -
                    {} # can retry on another node
         
     | 
| 
       32 
29 
     | 
    
         
             
                  end
         
     | 
| 
       33 
30 
     | 
    
         | 
| 
       34 
31 
     | 
    
         
             
                  private_class_method :fetch_node_info
         
     | 
    
        data/lib/redis/cluster/option.rb
    CHANGED
    
    | 
         @@ -17,6 +17,7 @@ class Redis 
     | 
|
| 
       17 
17 
     | 
    
         
             
                    node_addrs = options.delete(:cluster)
         
     | 
| 
       18 
18 
     | 
    
         
             
                    @node_opts = build_node_options(node_addrs)
         
     | 
| 
       19 
19 
     | 
    
         
             
                    @replica = options.delete(:replica) == true
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @fixed_hostname = options.delete(:fixed_hostname)
         
     | 
| 
       20 
21 
     | 
    
         
             
                    add_common_node_option_if_needed(options, @node_opts, :scheme)
         
     | 
| 
       21 
22 
     | 
    
         
             
                    add_common_node_option_if_needed(options, @node_opts, :username)
         
     | 
| 
       22 
23 
     | 
    
         
             
                    add_common_node_option_if_needed(options, @node_opts, :password)
         
     | 
| 
         @@ -24,8 +25,12 @@ class Redis 
     | 
|
| 
       24 
25 
     | 
    
         
             
                  end
         
     | 
| 
       25 
26 
     | 
    
         | 
| 
       26 
27 
     | 
    
         
             
                  def per_node_key
         
     | 
| 
       27 
     | 
    
         
            -
                    @node_opts.map  
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
      
 28 
     | 
    
         
            +
                    @node_opts.map do |opt|
         
     | 
| 
      
 29 
     | 
    
         
            +
                      node_key = NodeKey.build_from_host_port(opt[:host], opt[:port])
         
     | 
| 
      
 30 
     | 
    
         
            +
                      options = @options.merge(opt)
         
     | 
| 
      
 31 
     | 
    
         
            +
                      options = options.merge(host: @fixed_hostname) if @fixed_hostname && !@fixed_hostname.empty?
         
     | 
| 
      
 32 
     | 
    
         
            +
                      [node_key, options]
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end.to_h
         
     | 
| 
       29 
34 
     | 
    
         
             
                  end
         
     | 
| 
       30 
35 
     | 
    
         | 
| 
       31 
36 
     | 
    
         
             
                  def use_replica?
         
     | 
| 
         @@ -64,8 +69,10 @@ class Redis 
     | 
|
| 
       64 
69 
     | 
    
         
             
                    raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
         
     | 
| 
       65 
70 
     | 
    
         | 
| 
       66 
71 
     | 
    
         
             
                    db = uri.path.split('/')[1]&.to_i
         
     | 
| 
      
 72 
     | 
    
         
            +
                    username = uri.user ? URI.decode_www_form_component(uri.user) : nil
         
     | 
| 
      
 73 
     | 
    
         
            +
                    password = uri.password ? URI.decode_www_form_component(uri.password) : nil
         
     | 
| 
       67 
74 
     | 
    
         | 
| 
       68 
     | 
    
         
            -
                    { scheme: uri.scheme, username:  
     | 
| 
      
 75 
     | 
    
         
            +
                    { scheme: uri.scheme, username: username, password: password, host: uri.host, port: uri.port, db: db }
         
     | 
| 
       69 
76 
     | 
    
         
             
                      .reject { |_, v| v.nil? || v == '' }
         
     | 
| 
       70 
77 
     | 
    
         
             
                  rescue URI::InvalidURIError => err
         
     | 
| 
       71 
78 
     | 
    
         
             
                    raise InvalidClientOptionError, err.message
         
     | 
| 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'redis/errors'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'redis/cluster/node_key'
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            class Redis
         
     | 
| 
       7 
7 
     | 
    
         
             
              class Cluster
         
     | 
| 
         @@ -10,16 +10,15 @@ class Redis 
     | 
|
| 
       10 
10 
     | 
    
         
             
                  module_function
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
                  def load(nodes)
         
     | 
| 
       13 
     | 
    
         
            -
                     
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                       
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
      
 13 
     | 
    
         
            +
                    errors = nodes.map do |node|
         
     | 
| 
      
 14 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 15 
     | 
    
         
            +
                        return fetch_slot_info(node)
         
     | 
| 
      
 16 
     | 
    
         
            +
                      rescue CannotConnectError, ConnectionError, CommandError => error
         
     | 
| 
      
 17 
     | 
    
         
            +
                        error
         
     | 
| 
      
 18 
     | 
    
         
            +
                      end
         
     | 
| 
       18 
19 
     | 
    
         
             
                    end
         
     | 
| 
       19 
20 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
                     
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
                    raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
         
     | 
| 
      
 21 
     | 
    
         
            +
                    raise InitialSetupError, errors
         
     | 
| 
       23 
22 
     | 
    
         
             
                  end
         
     | 
| 
       24 
23 
     | 
    
         | 
| 
       25 
24 
     | 
    
         
             
                  def fetch_slot_info(node)
         
     | 
| 
         @@ -27,8 +26,6 @@ class Redis 
     | 
|
| 
       27 
26 
     | 
    
         
             
                    node.call(%i[cluster slots])
         
     | 
| 
       28 
27 
     | 
    
         
             
                        .flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
         
     | 
| 
       29 
28 
     | 
    
         
             
                        .each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
         
     | 
| 
       30 
     | 
    
         
            -
                  rescue CannotConnectError, ConnectionError, CommandError
         
     | 
| 
       31 
     | 
    
         
            -
                    {} # can retry on another node
         
     | 
| 
       32 
29 
     | 
    
         
             
                  end
         
     | 
| 
       33 
30 
     | 
    
         | 
| 
       34 
31 
     | 
    
         
             
                  def parse_slot_info(arr, default_ip:)
         
     | 
    
        data/lib/redis/cluster.rb
    CHANGED
    
    | 
         @@ -137,6 +137,7 @@ class Redis 
     | 
|
| 
       137 
137 
     | 
    
         
             
                  when 'wait'     then @node.call_master(command, &block).reduce(:+)
         
     | 
| 
       138 
138 
     | 
    
         
             
                  when 'keys'     then @node.call_slave(command, &block).flatten.sort
         
     | 
| 
       139 
139 
     | 
    
         
             
                  when 'dbsize'   then @node.call_slave(command, &block).reduce(:+)
         
     | 
| 
      
 140 
     | 
    
         
            +
                  when 'scan'     then _scan(command, &block)
         
     | 
| 
       140 
141 
     | 
    
         
             
                  when 'lastsave' then @node.call_all(command, &block).sort
         
     | 
| 
       141 
142 
     | 
    
         
             
                  when 'role'     then @node.call_all(command, &block)
         
     | 
| 
       142 
143 
     | 
    
         
             
                  when 'config'   then send_config_command(command, &block)
         
     | 
| 
         @@ -238,6 +239,29 @@ class Redis 
     | 
|
| 
       238 
239 
     | 
    
         
             
                  raise
         
     | 
| 
       239 
240 
     | 
    
         
             
                end
         
     | 
| 
       240 
241 
     | 
    
         | 
| 
      
 242 
     | 
    
         
            +
                def _scan(command, &block)
         
     | 
| 
      
 243 
     | 
    
         
            +
                  input_cursor = Integer(command[1])
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
                  client_index = input_cursor % 256
         
     | 
| 
      
 246 
     | 
    
         
            +
                  raw_cursor = input_cursor >> 8
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
                  clients = @node.scale_reading_clients
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
                  client = clients[client_index]
         
     | 
| 
      
 251 
     | 
    
         
            +
                  return ['0', []] unless client
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
                  command[1] = raw_cursor.to_s
         
     | 
| 
      
 254 
     | 
    
         
            +
             
     | 
| 
      
 255 
     | 
    
         
            +
                  result_cursor, result_keys = client.call(command, &block)
         
     | 
| 
      
 256 
     | 
    
         
            +
                  result_cursor = Integer(result_cursor)
         
     | 
| 
      
 257 
     | 
    
         
            +
             
     | 
| 
      
 258 
     | 
    
         
            +
                  if result_cursor == 0
         
     | 
| 
      
 259 
     | 
    
         
            +
                    client_index += 1
         
     | 
| 
      
 260 
     | 
    
         
            +
                  end
         
     | 
| 
      
 261 
     | 
    
         
            +
             
     | 
| 
      
 262 
     | 
    
         
            +
                  [((result_cursor << 8) + client_index).to_s, result_keys]
         
     | 
| 
      
 263 
     | 
    
         
            +
                end
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
       241 
265 
     | 
    
         
             
                def assign_redirection_node(err_msg)
         
     | 
| 
       242 
266 
     | 
    
         
             
                  _, slot, node_key = err_msg.split(' ')
         
     | 
| 
       243 
267 
     | 
    
         
             
                  slot = slot.to_i
         
     | 
| 
         @@ -0,0 +1,63 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Redis
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Commands
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Bitmaps
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Sets or clears the bit at offset in the string value stored at key.
         
     | 
| 
      
 7 
     | 
    
         
            +
                  #
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # @param [String] key
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # @param [Integer] offset bit offset
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # @param [Integer] value bit value `0` or `1`
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # @return [Integer] the original bit value stored at `offset`
         
     | 
| 
      
 12 
     | 
    
         
            +
                  def setbit(key, offset, value)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    send_command([:setbit, key, offset, value])
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  # Returns the bit value at offset in the string value stored at key.
         
     | 
| 
      
 17 
     | 
    
         
            +
                  #
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # @param [String] key
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # @param [Integer] offset bit offset
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # @return [Integer] `0` or `1`
         
     | 
| 
      
 21 
     | 
    
         
            +
                  def getbit(key, offset)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    send_command([:getbit, key, offset])
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  # Count the number of set bits in a range of the string value stored at key.
         
     | 
| 
      
 26 
     | 
    
         
            +
                  #
         
     | 
| 
      
 27 
     | 
    
         
            +
                  # @param [String] key
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # @param [Integer] start start index
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # @param [Integer] stop stop index
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # @return [Integer] the number of bits set to 1
         
     | 
| 
      
 31 
     | 
    
         
            +
                  def bitcount(key, start = 0, stop = -1)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    send_command([:bitcount, key, start, stop])
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  # Perform a bitwise operation between strings and store the resulting string in a key.
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # @param [String] operation e.g. `and`, `or`, `xor`, `not`
         
     | 
| 
      
 38 
     | 
    
         
            +
                  # @param [String] destkey destination key
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # @param [String, Array<String>] keys one or more source keys to perform `operation`
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # @return [Integer] the length of the string stored in `destkey`
         
     | 
| 
      
 41 
     | 
    
         
            +
                  def bitop(operation, destkey, *keys)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    send_command([:bitop, operation, destkey, *keys])
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  # Return the position of the first bit set to 1 or 0 in a string.
         
     | 
| 
      
 46 
     | 
    
         
            +
                  #
         
     | 
| 
      
 47 
     | 
    
         
            +
                  # @param [String] key
         
     | 
| 
      
 48 
     | 
    
         
            +
                  # @param [Integer] bit whether to look for the first 1 or 0 bit
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # @param [Integer] start start index
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # @param [Integer] stop stop index
         
     | 
| 
      
 51 
     | 
    
         
            +
                  # @return [Integer] the position of the first 1/0 bit.
         
     | 
| 
      
 52 
     | 
    
         
            +
                  #                  -1 if looking for 1 and it is not found or start and stop are given.
         
     | 
| 
      
 53 
     | 
    
         
            +
                  def bitpos(key, bit, start = nil, stop = nil)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                    command = [:bitpos, key, bit]
         
     | 
| 
      
 57 
     | 
    
         
            +
                    command << start if start
         
     | 
| 
      
 58 
     | 
    
         
            +
                    command << stop if stop
         
     | 
| 
      
 59 
     | 
    
         
            +
                    send_command(command)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Redis
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Commands
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Cluster
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Sends `CLUSTER *` command to random node and returns its reply.
         
     | 
| 
      
 7 
     | 
    
         
            +
                  #
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # @see https://redis.io/commands#cluster Reference of cluster command
         
     | 
| 
      
 9 
     | 
    
         
            +
                  #
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # @param subcommand [String, Symbol] the subcommand of cluster command
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #   e.g. `:slots`, `:nodes`, `:slaves`, `:info`
         
     | 
| 
      
 12 
     | 
    
         
            +
                  #
         
     | 
| 
      
 13 
     | 
    
         
            +
                  # @return [Object] depends on the subcommand
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def cluster(subcommand, *args)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    subcommand = subcommand.to_s.downcase
         
     | 
| 
      
 16 
     | 
    
         
            +
                    block = case subcommand
         
     | 
| 
      
 17 
     | 
    
         
            +
                    when 'slots'
         
     | 
| 
      
 18 
     | 
    
         
            +
                      HashifyClusterSlots
         
     | 
| 
      
 19 
     | 
    
         
            +
                    when 'nodes'
         
     | 
| 
      
 20 
     | 
    
         
            +
                      HashifyClusterNodes
         
     | 
| 
      
 21 
     | 
    
         
            +
                    when 'slaves'
         
     | 
| 
      
 22 
     | 
    
         
            +
                      HashifyClusterSlaves
         
     | 
| 
      
 23 
     | 
    
         
            +
                    when 'info'
         
     | 
| 
      
 24 
     | 
    
         
            +
                      HashifyInfo
         
     | 
| 
      
 25 
     | 
    
         
            +
                    else
         
     | 
| 
      
 26 
     | 
    
         
            +
                      Noop
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
         
     | 
| 
      
 30 
     | 
    
         
            +
                    block = Noop unless @cluster_mode
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    send_command([:cluster, subcommand] + args, &block)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  # Sends `ASKING` command to random node and returns its reply.
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
         
     | 
| 
      
 38 
     | 
    
         
            +
                  #
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # @return [String] `'OK'`
         
     | 
| 
      
 40 
     | 
    
         
            +
                  def asking
         
     | 
| 
      
 41 
     | 
    
         
            +
                    send_command(%i[asking])
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Redis
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Commands
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Connection
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Authenticate to the server.
         
     | 
| 
      
 7 
     | 
    
         
            +
                  #
         
     | 
| 
      
 8 
     | 
    
         
            +
                  # @param [Array<String>] args includes both username and password
         
     | 
| 
      
 9 
     | 
    
         
            +
                  #   or only password
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # @return [String] `OK`
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # @see https://redis.io/commands/auth AUTH command
         
     | 
| 
      
 12 
     | 
    
         
            +
                  def auth(*args)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    send_command([:auth, *args])
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  # Ping the server.
         
     | 
| 
      
 17 
     | 
    
         
            +
                  #
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # @param [optional, String] message
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # @return [String] `PONG`
         
     | 
| 
      
 20 
     | 
    
         
            +
                  def ping(message = nil)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    send_command([:ping, message].compact)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  # Echo the given string.
         
     | 
| 
      
 25 
     | 
    
         
            +
                  #
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # @param [String] value
         
     | 
| 
      
 27 
     | 
    
         
            +
                  # @return [String]
         
     | 
| 
      
 28 
     | 
    
         
            +
                  def echo(value)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    send_command([:echo, value])
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  # Change the selected database for the current connection.
         
     | 
| 
      
 33 
     | 
    
         
            +
                  #
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # @param [Integer] db zero-based index of the DB to use (0 to 15)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # @return [String] `OK`
         
     | 
| 
      
 36 
     | 
    
         
            +
                  def select(db)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    synchronize do |client|
         
     | 
| 
      
 38 
     | 
    
         
            +
                      client.db = db
         
     | 
| 
      
 39 
     | 
    
         
            +
                      client.call([:select, db])
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  # Close the connection.
         
     | 
| 
      
 44 
     | 
    
         
            +
                  #
         
     | 
| 
      
 45 
     | 
    
         
            +
                  # @return [String] `OK`
         
     | 
| 
      
 46 
     | 
    
         
            +
                  def quit
         
     | 
| 
      
 47 
     | 
    
         
            +
                    synchronize do |client|
         
     | 
| 
      
 48 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 49 
     | 
    
         
            +
                        client.call([:quit])
         
     | 
| 
      
 50 
     | 
    
         
            +
                      rescue ConnectionError
         
     | 
| 
      
 51 
     | 
    
         
            +
                      ensure
         
     | 
| 
      
 52 
     | 
    
         
            +
                        client.disconnect
         
     | 
| 
      
 53 
     | 
    
         
            +
                      end
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     |