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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1e483240f70aa8a92a7fe1f38968262ea82e84a97e436a9c7c695c032584313
4
- data.tar.gz: 1cb164b2d588ef2f2701b91371a12a0d90299a78994f0d614cb14ab0daa36243
3
+ metadata.gz: 5236e71d674779c964e99ebf2eac29efe80339a93de6907acfcd230a5d312153
4
+ data.tar.gz: 208b4bf708d70a570c354da2d5570972416cbe6007e1f8d99c31f3ef4958a342
5
5
  SHA512:
6
- metadata.gz: 72c3e91839061ae26e27cbb08330f752acbba5f440c8549cad4325b2c2f517a907a0db11330ca9ba221255aa7b570efe2050bde9cbb905217794c4754c53dadb
7
- data.tar.gz: e92cc58111fb9e1553a3363c56569ef6064c3851263d5eb86147c907878650b697db5c598b2b2046f9c9025cc40ca7eda5ce16f4f21049ac49f09385cf80a92f
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
- redis.set "foo", "bar"
189
- redis.incr "baz"
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
- redis.set "foo", "bar"
205
- redis.incr "baz"
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 inside a pipeline block return a
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 = redis.set "foo", "bar"
221
- @incr = redis.incr "baz"
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
- if err.message.match?(/ERR wrong number of arguments for \'auth\' command/)
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] || "redis://#{location}/#{db}"
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, timeout, &blk)
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.keys.each do |key|
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
- if uri.scheme == "unix"
462
+ case uri.scheme
463
+ when "unix"
453
464
  defaults[:path] = uri.path
454
- elsif uri.scheme == "redis" || uri.scheme == "rediss"
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.keys.each do |key|
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.map do |command, detail|
35
- [command, {
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.to_h
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
- require_relative '../errors'
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.each do |node|
13
+ errors = nodes.map do |node|
14
14
  begin
15
15
  return fetch_command_details(node)
16
- rescue CannotConnectError, ConnectionError, CommandError
17
- next # can retry on another node
16
+ rescue CannotConnectError, ConnectionError, CommandError => error
17
+ error
18
18
  end
19
19
  end
20
20
 
21
- raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
21
+ raise InitialSetupError, errors
22
22
  end
23
23
 
24
24
  def fetch_command_details(node)
@@ -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
- require_relative '../errors'
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
- info = {}
13
-
14
- nodes.each do |node|
15
- info = fetch_node_info(node)
16
- info.empty? ? next : break
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
- return info unless info.empty?
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
@@ -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 { |opt| [NodeKey.build_from_host_port(opt[:host], opt[:port]), @options.merge(opt)] }
28
- .to_h
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: uri.user, password: uri.password, host: uri.host, port: uri.port, db: db }
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
- require_relative '../errors'
4
- require_relative 'node_key'
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
- info = {}
14
-
15
- nodes.each do |node|
16
- info = fetch_slot_info(node)
17
- info.empty? ? next : break
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
- return info unless info.empty?
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