redis 4.8.1 → 6.0.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -1
  3. data/README.md +285 -169
  4. data/lib/redis/client.rb +116 -611
  5. data/lib/redis/commands/bitmaps.rb +14 -4
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +109 -7
  9. data/lib/redis/commands/hashes.rb +179 -8
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +32 -24
  12. data/lib/redis/commands/lists.rb +167 -25
  13. data/lib/redis/commands/modules/json.rb +530 -0
  14. data/lib/redis/commands/modules/search/aggregation.rb +418 -0
  15. data/lib/redis/commands/modules/search/dialect.rb +14 -0
  16. data/lib/redis/commands/modules/search/field.rb +306 -0
  17. data/lib/redis/commands/modules/search/hybrid.rb +359 -0
  18. data/lib/redis/commands/modules/search/index.rb +351 -0
  19. data/lib/redis/commands/modules/search/index_definition.rb +114 -0
  20. data/lib/redis/commands/modules/search/miscellaneous.rb +607 -0
  21. data/lib/redis/commands/modules/search/query.rb +738 -0
  22. data/lib/redis/commands/modules/search/result.rb +488 -0
  23. data/lib/redis/commands/modules/search/schema.rb +211 -0
  24. data/lib/redis/commands/modules/search.rb +19 -0
  25. data/lib/redis/commands/pubsub.rb +34 -25
  26. data/lib/redis/commands/server.rb +15 -15
  27. data/lib/redis/commands/sets.rb +76 -40
  28. data/lib/redis/commands/sorted_sets.rb +128 -19
  29. data/lib/redis/commands/streams.rb +75 -28
  30. data/lib/redis/commands/strings.rb +18 -17
  31. data/lib/redis/commands/transactions.rb +7 -31
  32. data/lib/redis/commands.rb +39 -20
  33. data/lib/redis/distributed.rb +407 -73
  34. data/lib/redis/errors.rb +20 -50
  35. data/lib/redis/hash_ring.rb +26 -26
  36. data/lib/redis/lib_identity.rb +105 -0
  37. data/lib/redis/pipeline.rb +47 -222
  38. data/lib/redis/subscribe.rb +51 -15
  39. data/lib/redis/version.rb +1 -1
  40. data/lib/redis.rb +213 -188
  41. metadata +25 -59
  42. data/lib/redis/cluster/command.rb +0 -79
  43. data/lib/redis/cluster/command_loader.rb +0 -33
  44. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  45. data/lib/redis/cluster/node.rb +0 -120
  46. data/lib/redis/cluster/node_key.rb +0 -31
  47. data/lib/redis/cluster/node_loader.rb +0 -34
  48. data/lib/redis/cluster/option.rb +0 -100
  49. data/lib/redis/cluster/slot.rb +0 -86
  50. data/lib/redis/cluster/slot_loader.rb +0 -46
  51. data/lib/redis/cluster.rb +0 -315
  52. data/lib/redis/connection/command_helper.rb +0 -41
  53. data/lib/redis/connection/hiredis.rb +0 -68
  54. data/lib/redis/connection/registry.rb +0 -13
  55. data/lib/redis/connection/ruby.rb +0 -437
  56. data/lib/redis/connection/synchrony.rb +0 -148
  57. data/lib/redis/connection.rb +0 -11
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redis/commands/modules/search/dialect"
4
+ require "redis/commands/modules/search/index_definition"
5
+ require "redis/commands/modules/search/field"
6
+ require "redis/commands/modules/search/schema"
7
+ require "redis/commands/modules/search/query"
8
+ require "redis/commands/modules/search/index"
9
+ require "redis/commands/modules/search/result"
10
+ require "redis/commands/modules/search/miscellaneous"
11
+ require "redis/commands/modules/search/hybrid"
12
+ require "redis/commands/modules/search/aggregation"
13
+
14
+ class Redis
15
+ module Commands
16
+ module Search
17
+ end
18
+ end
19
+ end
@@ -9,57 +9,45 @@ class Redis
9
9
  end
10
10
 
11
11
  def subscribed?
12
- synchronize do |client|
13
- client.is_a? SubscribedClient
14
- end
12
+ !@subscription_client.nil?
15
13
  end
16
14
 
17
15
  # Listen for messages published to the given channels.
18
16
  def subscribe(*channels, &block)
19
- synchronize do |_client|
20
- _subscription(:subscribe, 0, channels, block)
21
- end
17
+ _subscription(:subscribe, 0, channels, block)
22
18
  end
23
19
 
24
20
  # Listen for messages published to the given channels. Throw a timeout error
25
21
  # if there is no messages for a timeout period.
26
22
  def subscribe_with_timeout(timeout, *channels, &block)
27
- synchronize do |_client|
28
- _subscription(:subscribe_with_timeout, timeout, channels, block)
29
- end
23
+ _subscription(:subscribe_with_timeout, timeout, channels, block)
30
24
  end
31
25
 
32
26
  # Stop listening for messages posted to the given channels.
33
27
  def unsubscribe(*channels)
34
- synchronize do |client|
35
- raise "Can't unsubscribe if not subscribed." unless subscribed?
36
-
37
- client.unsubscribe(*channels)
38
- end
28
+ _subscription(:unsubscribe, 0, channels, nil)
39
29
  end
40
30
 
41
31
  # Listen for messages published to channels matching the given patterns.
32
+ # See the [Redis Server PSUBSCRIBE documentation](https://redis.io/docs/latest/commands/psubscribe/)
33
+ # for further details
42
34
  def psubscribe(*channels, &block)
43
- synchronize do |_client|
44
- _subscription(:psubscribe, 0, channels, block)
45
- end
35
+ _subscription(:psubscribe, 0, channels, block)
46
36
  end
47
37
 
48
38
  # Listen for messages published to channels matching the given patterns.
49
39
  # Throw a timeout error if there is no messages for a timeout period.
40
+ # See the [Redis Server PSUBSCRIBE documentation](https://redis.io/docs/latest/commands/psubscribe/)
41
+ # for further details
50
42
  def psubscribe_with_timeout(timeout, *channels, &block)
51
- synchronize do |_client|
52
- _subscription(:psubscribe_with_timeout, timeout, channels, block)
53
- end
43
+ _subscription(:psubscribe_with_timeout, timeout, channels, block)
54
44
  end
55
45
 
56
46
  # Stop listening for messages posted to channels matching the given patterns.
47
+ # See the [Redis Server PUNSUBSCRIBE documentation](https://redis.io/docs/latest/commands/punsubscribe/)
48
+ # for further details
57
49
  def punsubscribe(*channels)
58
- synchronize do |client|
59
- raise "Can't unsubscribe if not subscribed." unless subscribed?
60
-
61
- client.punsubscribe(*channels)
62
- end
50
+ _subscription(:punsubscribe, 0, channels, nil)
63
51
  end
64
52
 
65
53
  # Inspect the state of the Pub/Sub subsystem.
@@ -67,6 +55,27 @@ class Redis
67
55
  def pubsub(subcommand, *args)
68
56
  send_command([:pubsub, subcommand] + args)
69
57
  end
58
+
59
+ # Post a message to a channel in a shard.
60
+ def spublish(channel, message)
61
+ send_command([:spublish, channel, message])
62
+ end
63
+
64
+ # Listen for messages published to the given channels in a shard.
65
+ def ssubscribe(*channels, &block)
66
+ _subscription(:ssubscribe, 0, channels, block)
67
+ end
68
+
69
+ # Listen for messages published to the given channels in a shard.
70
+ # Throw a timeout error if there is no messages for a timeout period.
71
+ def ssubscribe_with_timeout(timeout, *channels, &block)
72
+ _subscription(:ssubscribe_with_timeout, timeout, channels, block)
73
+ end
74
+
75
+ # Stop listening for messages posted to the given channels in a shard.
76
+ def sunsubscribe(*channels)
77
+ _subscription(:sunsubscribe, 0, channels, nil)
78
+ end
70
79
  end
71
80
  end
72
81
  end
@@ -36,7 +36,7 @@ class Redis
36
36
  #
37
37
  # @param [String, Symbol] subcommand e.g. `kill`, `list`, `getname`, `setname`
38
38
  # @return [String, Hash] depends on subcommand
39
- def client(subcommand = nil, *args)
39
+ def client(subcommand, *args)
40
40
  send_command([:client, subcommand] + args) do |reply|
41
41
  if subcommand.to_s == "list"
42
42
  reply.lines.map do |line|
@@ -117,9 +117,13 @@ class Redis
117
117
  #
118
118
  # @yield a block to be called for every line of output
119
119
  # @yieldparam [String] line timestamp and command that was executed
120
- def monitor(&block)
120
+ def monitor
121
121
  synchronize do |client|
122
- client.call_loop([:monitor], &block)
122
+ client = client.pubsub
123
+ client.call_v([:monitor])
124
+ loop do
125
+ yield client.next_event
126
+ end
123
127
  end
124
128
  end
125
129
 
@@ -133,13 +137,11 @@ class Redis
133
137
  # Synchronously save the dataset to disk and then shut down the server.
134
138
  def shutdown
135
139
  synchronize do |client|
136
- client.with_reconnect(false) do
137
- begin
138
- client.call([:shutdown])
139
- rescue ConnectionError
140
- # This means Redis has probably exited.
141
- nil
142
- end
140
+ client.disable_reconnection do
141
+ client.call_v([:shutdown])
142
+ rescue ConnectionError
143
+ # This means Redis has probably exited.
144
+ nil
143
145
  end
144
146
  end
145
147
  end
@@ -155,11 +157,9 @@ class Redis
155
157
  # @param [Integer] length maximum number of entries to return
156
158
  # @return [Array<String>, Integer, String] depends on subcommand
157
159
  def slowlog(subcommand, length = nil)
158
- synchronize do |client|
159
- args = [:slowlog, subcommand]
160
- args << length if length
161
- client.call args
162
- end
160
+ args = [:slowlog, subcommand]
161
+ args << Integer(length) if length
162
+ send_command(args)
163
163
  end
164
164
 
165
165
  # Internal command used for replication.
@@ -15,56 +15,40 @@ class Redis
15
15
  #
16
16
  # @param [String] key
17
17
  # @param [String, Array<String>] member one member, or array of members
18
- # @return [Boolean, Integer] `Boolean` when a single member is specified,
19
- # holding whether or not adding the member succeeded, or `Integer` when an
20
- # array of members is specified, holding the number of members that were
21
- # successfully added
22
- def sadd(key, member)
23
- block = if Redis.sadd_returns_boolean && !member.is_a?(Array)
24
- ::Redis.deprecate!(
25
- "Redis#sadd will always return an Integer in Redis 5.0.0. Use Redis#sadd? instead." \
26
- "(called from: #{caller(1, 1).first})"
27
- )
28
- Boolify
29
- end
30
- send_command([:sadd, key, member], &block)
18
+ # @return [Integer] The number of members that were successfully added
19
+ def sadd(key, *members)
20
+ members.flatten!(1)
21
+ send_command([:sadd, key].concat(members))
31
22
  end
32
23
 
33
24
  # Add one or more members to a set.
34
25
  #
35
26
  # @param [String] key
36
27
  # @param [String, Array<String>] member one member, or array of members
37
- # @return [Boolean] Whether or not at least one member was added.
38
- def sadd?(key, member)
39
- send_command([:sadd, key, member], &Boolify)
28
+ # @return [Boolean] Wether at least one member was successfully added.
29
+ def sadd?(key, *members)
30
+ members.flatten!(1)
31
+ send_command([:sadd, key].concat(members), &Boolify)
40
32
  end
41
33
 
42
34
  # Remove one or more members from a set.
43
35
  #
44
36
  # @param [String] key
45
37
  # @param [String, Array<String>] member one member, or array of members
46
- # @return [Boolean, Integer] `Boolean` when a single member is specified,
47
- # holding whether or not removing the member succeeded, or `Integer` when an
48
- # array of members is specified, holding the number of members that were
49
- # successfully removed
50
- def srem(key, member)
51
- block = if Redis.sadd_returns_boolean && !member.is_a?(Array)
52
- ::Redis.deprecate!(
53
- "Redis#srem will always return an Integer in Redis 5.0.0. Use Redis#srem? instead." \
54
- "(called from: #{caller(1, 1).first})"
55
- )
56
- Boolify
57
- end
58
- send_command([:srem, key, member], &block)
38
+ # @return [Integer] The number of members that were successfully removed
39
+ def srem(key, *members)
40
+ members.flatten!(1)
41
+ send_command([:srem, key].concat(members))
59
42
  end
60
43
 
61
44
  # Remove one or more members from a set.
62
45
  #
63
46
  # @param [String] key
64
47
  # @param [String, Array<String>] member one member, or array of members
65
- # @return [Boolean] `Boolean` Whether or not a member was removed.
66
- def srem?(key, member)
67
- send_command([:srem, key, member], &Boolify)
48
+ # @return [Boolean] Wether at least one member was successfully removed.
49
+ def srem?(key, *members)
50
+ members.flatten!(1)
51
+ send_command([:srem, key].concat(members), &Boolify)
68
52
  end
69
53
 
70
54
  # Remove and return one or more random member from a set.
@@ -76,7 +60,7 @@ class Redis
76
60
  if count.nil?
77
61
  send_command([:spop, key])
78
62
  else
79
- send_command([:spop, key, count])
63
+ send_command([:spop, key, Integer(count)])
80
64
  end
81
65
  end
82
66
 
@@ -118,7 +102,8 @@ class Redis
118
102
  # @param [String, Array<String>] members
119
103
  # @return [Array<Boolean>]
120
104
  def smismember(key, *members)
121
- send_command([:smismember, key, *members]) do |reply|
105
+ members.flatten!(1)
106
+ send_command([:smismember, key].concat(members)) do |reply|
122
107
  reply.map(&Boolify)
123
108
  end
124
109
  end
@@ -136,7 +121,8 @@ class Redis
136
121
  # @param [String, Array<String>] keys keys pointing to sets to subtract
137
122
  # @return [Array<String>] members in the difference
138
123
  def sdiff(*keys)
139
- send_command([:sdiff, *keys])
124
+ keys.flatten!(1)
125
+ send_command([:sdiff].concat(keys))
140
126
  end
141
127
 
142
128
  # Subtract multiple sets and store the resulting set in a key.
@@ -145,7 +131,28 @@ class Redis
145
131
  # @param [String, Array<String>] keys keys pointing to sets to subtract
146
132
  # @return [Integer] number of elements in the resulting set
147
133
  def sdiffstore(destination, *keys)
148
- send_command([:sdiffstore, destination, *keys])
134
+ keys.flatten!(1)
135
+ send_command([:sdiffstore, destination].concat(keys))
136
+ end
137
+
138
+ # Get the number of members in the difference between the first set and
139
+ # all successive sets.
140
+ #
141
+ # @example
142
+ # redis.sadd("foo", ["s1", "s2", "s3"])
143
+ # redis.sadd("bar", "s3")
144
+ # redis.sdiffcard("foo", "bar")
145
+ # # => 2
146
+ #
147
+ # @param [String, Array<String>] keys keys pointing to sets to subtract
148
+ # @param [Integer] limit stop counting once the difference reaches `limit`
149
+ # members (`0` means unlimited)
150
+ # @return [Integer] number of members in the difference
151
+ def sdiffcard(*keys, limit: nil)
152
+ keys.flatten!(1)
153
+ args = [:sdiffcard, keys.size].concat(keys)
154
+ args << "LIMIT" << Integer(limit) if limit
155
+ send_command(args)
149
156
  end
150
157
 
151
158
  # Intersect multiple sets.
@@ -153,7 +160,8 @@ class Redis
153
160
  # @param [String, Array<String>] keys keys pointing to sets to intersect
154
161
  # @return [Array<String>] members in the intersection
155
162
  def sinter(*keys)
156
- send_command([:sinter, *keys])
163
+ keys.flatten!(1)
164
+ send_command([:sinter].concat(keys))
157
165
  end
158
166
 
159
167
  # Intersect multiple sets and store the resulting set in a key.
@@ -162,7 +170,8 @@ class Redis
162
170
  # @param [String, Array<String>] keys keys pointing to sets to intersect
163
171
  # @return [Integer] number of elements in the resulting set
164
172
  def sinterstore(destination, *keys)
165
- send_command([:sinterstore, destination, *keys])
173
+ keys.flatten!(1)
174
+ send_command([:sinterstore, destination].concat(keys))
166
175
  end
167
176
 
168
177
  # Add multiple sets.
@@ -170,7 +179,8 @@ class Redis
170
179
  # @param [String, Array<String>] keys keys pointing to sets to unify
171
180
  # @return [Array<String>] members in the union
172
181
  def sunion(*keys)
173
- send_command([:sunion, *keys])
182
+ keys.flatten!(1)
183
+ send_command([:sunion].concat(keys))
174
184
  end
175
185
 
176
186
  # Add multiple sets and store the resulting set in a key.
@@ -179,7 +189,29 @@ class Redis
179
189
  # @param [String, Array<String>] keys keys pointing to sets to unify
180
190
  # @return [Integer] number of elements in the resulting set
181
191
  def sunionstore(destination, *keys)
182
- send_command([:sunionstore, destination, *keys])
192
+ keys.flatten!(1)
193
+ send_command([:sunionstore, destination].concat(keys))
194
+ end
195
+
196
+ # Get the number of distinct members in the union of multiple sets.
197
+ #
198
+ # @example
199
+ # redis.sadd("foo", ["s1", "s2", "s3"])
200
+ # redis.sadd("bar", ["s3", "s4"])
201
+ # redis.sunioncard("foo", "bar")
202
+ # # => 4
203
+ #
204
+ # @param [String, Array<String>] keys keys pointing to sets to unify
205
+ # @param [Boolean] approx return an approximate cardinality computed with HyperLogLog
206
+ # @param [Integer] limit stop counting once the union reaches `limit`
207
+ # members (`0` means unlimited)
208
+ # @return [Integer] number of distinct members in the union
209
+ def sunioncard(*keys, approx: false, limit: nil)
210
+ keys.flatten!(1)
211
+ args = [:sunioncard, keys.size].concat(keys)
212
+ args << "APPROX" if approx
213
+ args << "LIMIT" << Integer(limit) if limit
214
+ send_command(args)
183
215
  end
184
216
 
185
217
  # Scan a set
@@ -193,6 +225,8 @@ class Redis
193
225
  # - `:count => Integer`: return count keys at most per iteration
194
226
  #
195
227
  # @return [String, Array<String>] the next cursor and all found members
228
+ #
229
+ # See the [Redis Server SSCAN documentation](https://redis.io/docs/latest/commands/sscan/) for further details
196
230
  def sscan(key, cursor, **options)
197
231
  _scan(:sscan, cursor, [key], **options)
198
232
  end
@@ -208,6 +242,8 @@ class Redis
208
242
  # - `:count => Integer`: return count keys at most per iteration
209
243
  #
210
244
  # @return [Enumerator] an enumerator for all keys in the set
245
+ #
246
+ # See the [Redis Server SSCAN documentation](https://redis.io/docs/latest/commands/sscan/) for further details
211
247
  def sscan_each(key, **options, &block)
212
248
  return to_enum(:sscan_each, key, **options) unless block_given?
213
249
 
@@ -136,7 +136,9 @@ class Redis
136
136
  # @return [Array<String, Float>] element and score pair if count is not specified
137
137
  # @return [Array<Array<String, Float>>] list of popped elements and scores
138
138
  def zpopmax(key, count = nil)
139
- send_command([:zpopmax, key, count].compact) do |members|
139
+ command = [:zpopmax, key]
140
+ command << Integer(count) if count
141
+ send_command(command) do |members|
140
142
  members = FloatifyPairs.call(members)
141
143
  count.to_i > 1 ? members : members.first
142
144
  end
@@ -157,12 +159,80 @@ class Redis
157
159
  # @return [Array<String, Float>] element and score pair if count is not specified
158
160
  # @return [Array<Array<String, Float>>] list of popped elements and scores
159
161
  def zpopmin(key, count = nil)
160
- send_command([:zpopmin, key, count].compact) do |members|
162
+ command = [:zpopmin, key]
163
+ command << Integer(count) if count
164
+ send_command(command) do |members|
161
165
  members = FloatifyPairs.call(members)
162
166
  count.to_i > 1 ? members : members.first
163
167
  end
164
168
  end
165
169
 
170
+ # Removes and returns up to count members with scores in the sorted set stored at key.
171
+ #
172
+ # @example Popping a member
173
+ # redis.bzmpop('zset')
174
+ # #=> ['zset', ['a', 1.0]]
175
+ # @example With count option
176
+ # redis.bzmpop('zset', count: 2)
177
+ # #=> ['zset', [['a', 1.0], ['b', 2.0]]
178
+ #
179
+ # @params timeout [Float] a float value specifying the maximum number of seconds to block) elapses.
180
+ # A timeout of zero can be used to block indefinitely.
181
+ # @params key [String, Array<String>] one or more keys with sorted sets
182
+ # @params modifier [String]
183
+ # - when `"MIN"` - the elements popped are those with lowest scores
184
+ # - when `"MAX"` - the elements popped are those with the highest scores
185
+ # @params count [Integer] a number of members to pop
186
+ #
187
+ # @return [Array<String, Array<String, Float>>] list of popped elements and scores
188
+ def bzmpop(timeout, *keys, modifier: "MIN", count: nil)
189
+ raise ArgumentError, "Pick either MIN or MAX" unless modifier == "MIN" || modifier == "MAX"
190
+
191
+ args = [:bzmpop, timeout, keys.size, *keys, modifier]
192
+ args << "COUNT" << Integer(count) if count
193
+
194
+ send_blocking_command(args, timeout) do |response|
195
+ response&.map do |entry|
196
+ case entry
197
+ when String then entry
198
+ when Array then entry.map { |pair| FloatifyPairs.call(pair) }.flatten(1)
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ # Removes and returns up to count members with scores in the sorted set stored at key.
205
+ #
206
+ # @example Popping a member
207
+ # redis.zmpop('zset')
208
+ # #=> ['zset', ['a', 1.0]]
209
+ # @example With count option
210
+ # redis.zmpop('zset', count: 2)
211
+ # #=> ['zset', [['a', 1.0], ['b', 2.0]]
212
+ #
213
+ # @params key [String, Array<String>] one or more keys with sorted sets
214
+ # @params modifier [String]
215
+ # - when `"MIN"` - the elements popped are those with lowest scores
216
+ # - when `"MAX"` - the elements popped are those with the highest scores
217
+ # @params count [Integer] a number of members to pop
218
+ #
219
+ # @return [Array<String, Array<String, Float>>] list of popped elements and scores
220
+ def zmpop(*keys, modifier: "MIN", count: nil)
221
+ raise ArgumentError, "Pick either MIN or MAX" unless modifier == "MIN" || modifier == "MAX"
222
+
223
+ args = [:zmpop, keys.size, *keys, modifier]
224
+ args << "COUNT" << Integer(count) if count
225
+
226
+ send_command(args) do |response|
227
+ response&.map do |entry|
228
+ case entry
229
+ when String then entry
230
+ when Array then entry.map { |pair| FloatifyPairs.call(pair) }.flatten(1)
231
+ end
232
+ end
233
+ end
234
+ end
235
+
166
236
  # Removes and returns up to count members with the highest scores in the sorted set stored at keys,
167
237
  # or block until one is available.
168
238
  #
@@ -261,7 +331,7 @@ class Redis
261
331
  end
262
332
 
263
333
  args = [:zrandmember, key]
264
- args << count if count
334
+ args << Integer(count) if count
265
335
 
266
336
  if with_scores
267
337
  args << "WITHSCORES"
@@ -296,7 +366,6 @@ class Redis
296
366
  # - when `:with_scores` is specified, an array with `[member, score]` pairs
297
367
  def zrange(key, start, stop, byscore: false, by_score: byscore, bylex: false, by_lex: bylex,
298
368
  rev: false, limit: nil, withscores: false, with_scores: withscores)
299
-
300
369
  if by_score && by_lex
301
370
  raise ArgumentError, "only one of :by_score or :by_lex can be specified"
302
371
  end
@@ -313,7 +382,7 @@ class Redis
313
382
 
314
383
  if limit
315
384
  args << "LIMIT"
316
- args.concat(limit)
385
+ args.concat(limit.map { |l| Integer(l) })
317
386
  end
318
387
 
319
388
  if with_scores
@@ -354,7 +423,7 @@ class Redis
354
423
 
355
424
  if limit
356
425
  args << "LIMIT"
357
- args.concat(limit)
426
+ args.concat(limit.map { |l| Integer(l) })
358
427
  end
359
428
 
360
429
  send_command(args)
@@ -372,7 +441,7 @@ class Redis
372
441
  #
373
442
  # @see #zrange
374
443
  def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
375
- args = [:zrevrange, key, start, stop]
444
+ args = [:zrevrange, key, Integer(start), Integer(stop)]
376
445
 
377
446
  if with_scores
378
447
  args << "WITHSCORES"
@@ -384,21 +453,55 @@ class Redis
384
453
 
385
454
  # Determine the index of a member in a sorted set.
386
455
  #
456
+ # @example Retrieve member rank
457
+ # redis.zrank("zset", "a")
458
+ # # => 3
459
+ # @example Retrieve member rank with their score
460
+ # redis.zrank("zset", "a", :with_score => true)
461
+ # # => [3, 32.0]
462
+ #
387
463
  # @param [String] key
388
464
  # @param [String] member
389
- # @return [Integer]
390
- def zrank(key, member)
391
- send_command([:zrank, key, member])
465
+ #
466
+ # @return [Integer, [Integer, Float]]
467
+ # - when `:with_score` is not specified, an Integer
468
+ # - when `:with_score` is specified, a `[rank, score]` pair
469
+ def zrank(key, member, withscore: false, with_score: withscore)
470
+ args = [:zrank, key, member]
471
+
472
+ if with_score
473
+ args << "WITHSCORE"
474
+ block = FloatifyPair
475
+ end
476
+
477
+ send_command(args, &block)
392
478
  end
393
479
 
394
480
  # Determine the index of a member in a sorted set, with scores ordered from
395
481
  # high to low.
396
482
  #
483
+ # @example Retrieve member rank
484
+ # redis.zrevrank("zset", "a")
485
+ # # => 3
486
+ # @example Retrieve member rank with their score
487
+ # redis.zrevrank("zset", "a", :with_score => true)
488
+ # # => [3, 32.0]
489
+ #
397
490
  # @param [String] key
398
491
  # @param [String] member
399
- # @return [Integer]
400
- def zrevrank(key, member)
401
- send_command([:zrevrank, key, member])
492
+ #
493
+ # @return [Integer, [Integer, Float]]
494
+ # - when `:with_score` is not specified, an Integer
495
+ # - when `:with_score` is specified, a `[rank, score]` pair
496
+ def zrevrank(key, member, withscore: false, with_score: withscore)
497
+ args = [:zrevrank, key, member]
498
+
499
+ if with_score
500
+ args << "WITHSCORE"
501
+ block = FloatifyPair
502
+ end
503
+
504
+ send_command(args, &block)
402
505
  end
403
506
 
404
507
  # Remove all members in a sorted set within the given indexes.
@@ -466,7 +569,7 @@ class Redis
466
569
 
467
570
  if limit
468
571
  args << "LIMIT"
469
- args.concat(limit)
572
+ args.concat(limit.map { |l| Integer(l) })
470
573
  end
471
574
 
472
575
  send_command(args)
@@ -488,7 +591,7 @@ class Redis
488
591
 
489
592
  if limit
490
593
  args << "LIMIT"
491
- args.concat(limit)
594
+ args.concat(limit.map { |l| Integer(l) })
492
595
  end
493
596
 
494
597
  send_command(args)
@@ -531,7 +634,7 @@ class Redis
531
634
 
532
635
  if limit
533
636
  args << "LIMIT"
534
- args.concat(limit)
637
+ args.concat(limit.map { |l| Integer(l) })
535
638
  end
536
639
 
537
640
  send_command(args, &block)
@@ -561,7 +664,7 @@ class Redis
561
664
 
562
665
  if limit
563
666
  args << "LIMIT"
564
- args.concat(limit)
667
+ args.concat(limit.map { |l| Integer(l) })
565
668
  end
566
669
 
567
670
  send_command(args, &block)
@@ -747,6 +850,8 @@ class Redis
747
850
  #
748
851
  # @return [String, Array<[String, Float]>] the next cursor and all found
749
852
  # members and scores
853
+ #
854
+ # See the [Redis Server ZSCAN documentation](https://redis.io/docs/latest/commands/zscan/) for further details
750
855
  def zscan(key, cursor, **options)
751
856
  _scan(:zscan, cursor, [key], **options) do |reply|
752
857
  [reply[0], FloatifyPairs.call(reply[1])]
@@ -764,6 +869,8 @@ class Redis
764
869
  # - `:count => Integer`: return count keys at most per iteration
765
870
  #
766
871
  # @return [Enumerator] an enumerator for all found scores and members
872
+ #
873
+ # See the [Redis Server ZSCAN documentation](https://redis.io/docs/latest/commands/zscan/) for further details
767
874
  def zscan_each(key, **options, &block)
768
875
  return to_enum(:zscan_each, key, **options) unless block_given?
769
876
 
@@ -778,7 +885,8 @@ class Redis
778
885
  private
779
886
 
780
887
  def _zsets_operation(cmd, *keys, weights: nil, aggregate: nil, with_scores: false)
781
- command = [cmd, keys.size, *keys]
888
+ keys.flatten!(1)
889
+ command = [cmd, keys.size].concat(keys)
782
890
 
783
891
  if weights
784
892
  command << "WEIGHTS"
@@ -796,7 +904,8 @@ class Redis
796
904
  end
797
905
 
798
906
  def _zsets_operation_store(cmd, destination, keys, weights: nil, aggregate: nil)
799
- command = [cmd, destination, keys.size, *keys]
907
+ keys.flatten!(1)
908
+ command = [cmd, destination, keys.size].concat(keys)
800
909
 
801
910
  if weights
802
911
  command << "WEIGHTS"