redis 4.5.1 → 4.6.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: 7a7232fef186e6d6a11a90d8dd9aa7c71114f017e0afe9378999a96c9e6b4e05
4
- data.tar.gz: 689f176b87909bf61eb60e57d1eb795198162a7e039104c80facf36880964bda
3
+ metadata.gz: 7e99f4e628112a227719d2000dc5b081893273cfbd51ae25bf00a8f6b6594061
4
+ data.tar.gz: 42a8e0cf75aebbc14cdf680b4bc1f7bbdec9e20b53f150c6443c01126960a959
5
5
  SHA512:
6
- metadata.gz: 22b11dee92e298b46bb94a707156d3dbf9afb83c8e9e8cbf82cf366d582a7c1b7295d7f09a0fec01965245f4800c2482c0559d646f46ff1c6bba6423ab398ba9
7
- data.tar.gz: 9a74ba29c8cb3d7634a44c78e30018a048e19cca66c7fad6a226722183f66546bebb370c92ecdf20522bb35ee72a09b2fda64891ff6d94db1702375bd1ba6b46
6
+ metadata.gz: 8bc57fe306c601f27d32df4940d5632e9e7d75529f6ac5d42732b21fe04452a4a1ab89567492c6fb7249ef4d69ebb2c0b7c985e18b0ef4f9ee6fb816c31bcbda
7
+ data.tar.gz: 42b2f8c584d6f96d0fc783d0b36af0fd9415d1dbd43ad8fe7a980688e52fbdb51645a07960d9cb3faacf6696c460cf38a013e290d0b58d4bbbb07626f18f15c7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # Unreleased
2
2
 
3
+ # 4.6.0
4
+
5
+ * Deprecate `Redis.current`.
6
+ * Deprecate calling commands on `Redis` inside `Redis#pipelined`. See #1059.
7
+ ```ruby
8
+ redis.pipelined do
9
+ redis.get("key")
10
+ end
11
+ ```
12
+
13
+ should be replaced by:
14
+
15
+ ```ruby
16
+ redis.pipelined do |pipeline|
17
+ pipeline.get("key")
18
+ end
19
+ ```
20
+ * Deprecate calling commands on `Redis` inside `Redis#multi`. See #1059.
21
+ ```ruby
22
+ redis.multi do
23
+ redis.get("key")
24
+ end
25
+ ```
26
+
27
+ should be replaced by:
28
+
29
+ ```ruby
30
+ redis.multi do |transaction|
31
+ transaction.get("key")
32
+ end
33
+ ```
34
+ * Deprecate `Redis#queue` and `Redis#commit`. See #1059.
35
+
36
+ * Fix `zpopmax` and `zpopmin` when called inside a pipeline. See #1055.
37
+ * `Redis#synchronize` is now private like it should always have been.
38
+
39
+ * Add `Redis.silence_deprecations=` to turn off deprecation warnings.
40
+ If you don't wish to see warnings yet, you can set `Redis.silence_deprecations = false`.
41
+ It is however heavily recommended to fix them instead when possible.
42
+ * Add `Redis.raise_deprecations=` to turn deprecation warnings into errors.
43
+ This makes it easier to identitify the source of deprecated APIs usage.
44
+ It is recommended to set `Redis.raise_deprecations = true` in development and test environments.
45
+ * Add new options to ZRANGE. See #1053.
46
+ * Add ZRANGESTORE command. See #1053.
47
+ * Add SCAN support for `Redis::Cluster`. See #1049.
48
+ * Add COPY command. See #1053. See #1048.
49
+ * Add ZDIFFSTORE command. See #1046.
50
+ * Add ZDIFF command. See #1044.
51
+ * Add ZUNION command. See #1042.
52
+ * Add HRANDFIELD command. See #1040.
53
+
3
54
  # 4.5.1
4
55
 
5
56
  * 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,
data/README.md CHANGED
@@ -184,9 +184,9 @@ commands to Redis and gathers their replies. These replies are returned
184
184
  by the `#pipelined` method.
185
185
 
186
186
  ```ruby
187
- redis.pipelined do
188
- redis.set "foo", "bar"
189
- redis.incr "baz"
187
+ redis.pipelined do |pipeline|
188
+ pipeline.set "foo", "bar"
189
+ pipeline.incr "baz"
190
190
  end
191
191
  # => ["OK", 1]
192
192
  ```
@@ -200,9 +200,9 @@ the regular pipeline, the replies to the commands are returned by the
200
200
  `#multi` method.
201
201
 
202
202
  ```ruby
203
- redis.multi do
204
- redis.set "foo", "bar"
205
- redis.incr "baz"
203
+ redis.multi do |transaction|
204
+ transaction.set "foo", "bar"
205
+ transaction.incr "baz"
206
206
  end
207
207
  # => ["OK", 1]
208
208
  ```
@@ -210,15 +210,15 @@ end
210
210
  ### Futures
211
211
 
212
212
  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
213
+ emit (since redis-rb 3.0). All calls on the pipeline object return a
214
214
  `Future` object, which responds to the `#value` method. When the
215
215
  pipeline has successfully executed, all futures are assigned their
216
216
  respective replies and can be used.
217
217
 
218
218
  ```ruby
219
- redis.pipelined do
220
- @set = redis.set "foo", "bar"
221
- @incr = redis.incr "baz"
219
+ redis.pipelined do |pipeline|
220
+ @set = pipeline.set "foo", "bar"
221
+ @incr = pipeline.incr "baz"
222
222
  end
223
223
 
224
224
  @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,17 +118,18 @@ 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]
125
- elsif err.message.match?(/WRONGPASS invalid username-password pair/)
124
+ when /WRONGPASS invalid username-password pair/
126
125
  begin
127
126
  call [:auth, password]
128
127
  rescue CommandError
129
128
  raise err
130
129
  end
131
- ::Kernel.warn(
130
+ ::Redis.deprecate!(
132
131
  "[redis-rb] The Redis connection was configured with username #{username.inspect}, but" \
133
- " the provided password was for the default user. This will start failing in redis-rb 4.6."
132
+ " the provided password was for the default user. This will start failing in redis-rb 5.0.0."
134
133
  )
135
134
  else
136
135
  raise
@@ -252,7 +251,8 @@ class Redis
252
251
  result
253
252
  end
254
253
 
255
- 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
256
256
  with_socket_timeout(timeout) do
257
257
  call(command, &blk)
258
258
  end
@@ -442,7 +442,7 @@ class Redis
442
442
  defaults = DEFAULTS.dup
443
443
  options = options.dup
444
444
 
445
- defaults.keys.each do |key|
445
+ defaults.each_key do |key|
446
446
  # Fill in defaults if needed
447
447
  defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
448
448
 
@@ -459,9 +459,10 @@ class Redis
459
459
 
460
460
  uri = URI(url)
461
461
 
462
- if uri.scheme == "unix"
462
+ case uri.scheme
463
+ when "unix"
463
464
  defaults[:path] = uri.path
464
- elsif uri.scheme == "redis" || uri.scheme == "rediss"
465
+ when "redis", "rediss"
465
466
  defaults[:scheme] = uri.scheme
466
467
  defaults[:host] = uri.host if uri.host
467
468
  defaults[:port] = uri.port if uri.port
@@ -477,7 +478,7 @@ class Redis
477
478
  end
478
479
 
479
480
  # Use default when option is not specified or nil
480
- defaults.keys.each do |key|
481
+ defaults.each_key do |key|
481
482
  options[key] = defaults[key] if options[key].nil?
482
483
  end
483
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
@@ -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?
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
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Geo
6
+ # Adds the specified geospatial items (latitude, longitude, name) to the specified key
7
+ #
8
+ # @param [String] key
9
+ # @param [Array] member arguemnts for member or members: longitude, latitude, name
10
+ # @return [Integer] number of elements added to the sorted set
11
+ def geoadd(key, *member)
12
+ send_command([:geoadd, key, *member])
13
+ end
14
+
15
+ # Returns geohash string representing position for specified members of the specified key.
16
+ #
17
+ # @param [String] key
18
+ # @param [String, Array<String>] member one member or array of members
19
+ # @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
20
+ def geohash(key, member)
21
+ send_command([:geohash, key, member])
22
+ end
23
+
24
+ # Query a sorted set representing a geospatial index to fetch members matching a
25
+ # given maximum distance from a point
26
+ #
27
+ # @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
28
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest
29
+ # or the farthest to the nearest relative to the center
30
+ # @param [Integer] count limit the results to the first N matching items
31
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
32
+ # @return [Array<String>] may be changed with `options`
33
+ def georadius(*args, **geoptions)
34
+ geoarguments = _geoarguments(*args, **geoptions)
35
+
36
+ send_command([:georadius, *geoarguments])
37
+ end
38
+
39
+ # Query a sorted set representing a geospatial index to fetch members matching a
40
+ # given maximum distance from an already existing member
41
+ #
42
+ # @param [Array] args key, member, radius, unit(m|km|ft|mi)
43
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest
44
+ # to the nearest relative to the center
45
+ # @param [Integer] count limit the results to the first N matching items
46
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
47
+ # @return [Array<String>] may be changed with `options`
48
+ def georadiusbymember(*args, **geoptions)
49
+ geoarguments = _geoarguments(*args, **geoptions)
50
+
51
+ send_command([:georadiusbymember, *geoarguments])
52
+ end
53
+
54
+ # Returns longitude and latitude of members of a geospatial index
55
+ #
56
+ # @param [String] key
57
+ # @param [String, Array<String>] member one member or array of members
58
+ # @return [Array<Array<String>, nil>] returns array of elements, where each
59
+ # element is either array of longitude and latitude or nil
60
+ def geopos(key, member)
61
+ send_command([:geopos, key, member])
62
+ end
63
+
64
+ # Returns the distance between two members of a geospatial index
65
+ #
66
+ # @param [String ]key
67
+ # @param [Array<String>] members
68
+ # @param ['m', 'km', 'mi', 'ft'] unit
69
+ # @return [String, nil] returns distance in spefied unit if both members present, nil otherwise.
70
+ def geodist(key, member1, member2, unit = 'm')
71
+ send_command([:geodist, key, member1, member2, unit])
72
+ end
73
+
74
+ private
75
+
76
+ def _geoarguments(*args, options: nil, sort: nil, count: nil)
77
+ args.push sort if sort
78
+ args.push 'count', count if count
79
+ args.push options if options
80
+ args
81
+ end
82
+ end
83
+ end
84
+ end