redis 4.5.1 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
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