redis 4.7.0 → 5.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +77 -161
  4. data/lib/redis/client.rb +78 -615
  5. data/lib/redis/commands/bitmaps.rb +4 -1
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/hashes.rb +6 -3
  9. data/lib/redis/commands/hyper_log_log.rb +1 -1
  10. data/lib/redis/commands/keys.rb +52 -26
  11. data/lib/redis/commands/lists.rb +10 -14
  12. data/lib/redis/commands/pubsub.rb +7 -25
  13. data/lib/redis/commands/server.rb +14 -14
  14. data/lib/redis/commands/sets.rb +42 -35
  15. data/lib/redis/commands/sorted_sets.rb +4 -2
  16. data/lib/redis/commands/streams.rb +12 -10
  17. data/lib/redis/commands/strings.rb +1 -0
  18. data/lib/redis/commands/transactions.rb +7 -31
  19. data/lib/redis/commands.rb +1 -8
  20. data/lib/redis/distributed.rb +100 -67
  21. data/lib/redis/errors.rb +11 -50
  22. data/lib/redis/hash_ring.rb +26 -26
  23. data/lib/redis/pipeline.rb +43 -222
  24. data/lib/redis/subscribe.rb +23 -15
  25. data/lib/redis/version.rb +1 -1
  26. data/lib/redis.rb +79 -183
  27. metadata +13 -57
  28. data/lib/redis/cluster/command.rb +0 -79
  29. data/lib/redis/cluster/command_loader.rb +0 -33
  30. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  31. data/lib/redis/cluster/node.rb +0 -120
  32. data/lib/redis/cluster/node_key.rb +0 -31
  33. data/lib/redis/cluster/node_loader.rb +0 -34
  34. data/lib/redis/cluster/option.rb +0 -100
  35. data/lib/redis/cluster/slot.rb +0 -86
  36. data/lib/redis/cluster/slot_loader.rb +0 -46
  37. data/lib/redis/cluster.rb +0 -315
  38. data/lib/redis/connection/command_helper.rb +0 -41
  39. data/lib/redis/connection/hiredis.rb +0 -66
  40. data/lib/redis/connection/registry.rb +0 -13
  41. data/lib/redis/connection/ruby.rb +0 -431
  42. data/lib/redis/connection/synchrony.rb +0 -148
  43. data/lib/redis/connection.rb +0 -11
@@ -39,7 +39,10 @@ class Redis
39
39
  # @param [String, Array<String>] keys one or more source keys to perform `operation`
40
40
  # @return [Integer] the length of the string stored in `destkey`
41
41
  def bitop(operation, destkey, *keys)
42
- send_command([:bitop, operation, destkey, *keys])
42
+ keys.flatten!(1)
43
+ command = [:bitop, operation, destkey]
44
+ command.concat(keys)
45
+ send_command(command)
43
46
  end
44
47
 
45
48
  # Return the position of the first bit set to 1 or 0 in a string.
@@ -12,24 +12,7 @@ class Redis
12
12
  #
13
13
  # @return [Object] depends on the subcommand
14
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)
15
+ send_command([:cluster, subcommand] + args)
33
16
  end
34
17
 
35
18
  # Sends `ASKING` command to random node and returns its reply.
@@ -34,10 +34,7 @@ class Redis
34
34
  # @param [Integer] db zero-based index of the DB to use (0 to 15)
35
35
  # @return [String] `OK`
36
36
  def select(db)
37
- synchronize do |client|
38
- client.db = db
39
- client.call([:select, db])
40
- end
37
+ send_command([:select, db])
41
38
  end
42
39
 
43
40
  # Close the connection.
@@ -45,12 +42,10 @@ class Redis
45
42
  # @return [String] `OK`
46
43
  def quit
47
44
  synchronize do |client|
48
- begin
49
- client.call([:quit])
50
- rescue ConnectionError
51
- ensure
52
- client.disconnect
53
- end
45
+ client.call_v([:quit])
46
+ rescue ConnectionError
47
+ ensure
48
+ client.close
54
49
  end
55
50
  end
56
51
  end
@@ -87,7 +87,8 @@ class Redis
87
87
  #
88
88
  # @see #mapped_hmget
89
89
  def hmget(key, *fields, &blk)
90
- send_command([:hmget, key] + fields, &blk)
90
+ fields.flatten!(1)
91
+ send_command([:hmget, key].concat(fields), &blk)
91
92
  end
92
93
 
93
94
  # Get the values of all the given hash fields.
@@ -102,7 +103,8 @@ class Redis
102
103
  #
103
104
  # @see #hmget
104
105
  def mapped_hmget(key, *fields)
105
- hmget(key, *fields) do |reply|
106
+ fields.flatten!(1)
107
+ hmget(key, fields) do |reply|
106
108
  if reply.is_a?(Array)
107
109
  Hash[fields.zip(reply)]
108
110
  else
@@ -152,7 +154,8 @@ class Redis
152
154
  # @param [String, Array<String>] field
153
155
  # @return [Integer] the number of fields that were removed from the hash
154
156
  def hdel(key, *fields)
155
- send_command([:hdel, key, *fields])
157
+ fields.flatten!(1)
158
+ send_command([:hdel, key].concat(fields))
156
159
  end
157
160
 
158
161
  # Determine if a hash field exists.
@@ -20,7 +20,7 @@ class Redis
20
20
  # @param [String, Array<String>] keys
21
21
  # @return [Integer]
22
22
  def pfcount(*keys)
23
- send_command([:pfcount] + keys)
23
+ send_command([:pfcount] + keys.flatten(1))
24
24
  end
25
25
 
26
26
  # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
@@ -69,18 +69,40 @@ class Redis
69
69
  #
70
70
  # @param [String] key
71
71
  # @param [Integer] seconds time to live
72
+ # @param [Hash] options
73
+ # - `:nx => true`: Set expiry only when the key has no expiry.
74
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
75
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
76
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
72
77
  # @return [Boolean] whether the timeout was set or not
73
- def expire(key, seconds)
74
- send_command([:expire, key, seconds], &Boolify)
78
+ def expire(key, seconds, nx: nil, xx: nil, gt: nil, lt: nil)
79
+ args = [:expire, key, seconds]
80
+ args << "NX" if nx
81
+ args << "XX" if xx
82
+ args << "GT" if gt
83
+ args << "LT" if lt
84
+
85
+ send_command(args, &Boolify)
75
86
  end
76
87
 
77
88
  # Set the expiration for a key as a UNIX timestamp.
78
89
  #
79
90
  # @param [String] key
80
91
  # @param [Integer] unix_time expiry time specified as a UNIX timestamp
92
+ # @param [Hash] options
93
+ # - `:nx => true`: Set expiry only when the key has no expiry.
94
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
95
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
96
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
81
97
  # @return [Boolean] whether the timeout was set or not
82
- def expireat(key, unix_time)
83
- send_command([:expireat, key, unix_time], &Boolify)
98
+ def expireat(key, unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
99
+ args = [:expireat, key, unix_time]
100
+ args << "NX" if nx
101
+ args << "XX" if xx
102
+ args << "GT" if gt
103
+ args << "LT" if lt
104
+
105
+ send_command(args, &Boolify)
84
106
  end
85
107
 
86
108
  # Get the time to live (in seconds) for a key.
@@ -103,18 +125,40 @@ class Redis
103
125
  #
104
126
  # @param [String] key
105
127
  # @param [Integer] milliseconds time to live
128
+ # @param [Hash] options
129
+ # - `:nx => true`: Set expiry only when the key has no expiry.
130
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
131
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
132
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
106
133
  # @return [Boolean] whether the timeout was set or not
107
- def pexpire(key, milliseconds)
108
- send_command([:pexpire, key, milliseconds], &Boolify)
134
+ def pexpire(key, milliseconds, nx: nil, xx: nil, gt: nil, lt: nil)
135
+ args = [:pexpire, key, milliseconds]
136
+ args << "NX" if nx
137
+ args << "XX" if xx
138
+ args << "GT" if gt
139
+ args << "LT" if lt
140
+
141
+ send_command(args, &Boolify)
109
142
  end
110
143
 
111
144
  # Set the expiration for a key as number of milliseconds from UNIX Epoch.
112
145
  #
113
146
  # @param [String] key
114
147
  # @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
148
+ # @param [Hash] options
149
+ # - `:nx => true`: Set expiry only when the key has no expiry.
150
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
151
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
152
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
115
153
  # @return [Boolean] whether the timeout was set or not
116
- def pexpireat(key, ms_unix_time)
117
- send_command([:pexpireat, key, ms_unix_time], &Boolify)
154
+ def pexpireat(key, ms_unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
155
+ args = [:pexpireat, key, ms_unix_time]
156
+ args << "NX" if nx
157
+ args << "XX" if xx
158
+ args << "GT" if gt
159
+ args << "LT" if lt
160
+
161
+ send_command(args, &Boolify)
118
162
  end
119
163
 
120
164
  # Get the time to live (in milliseconds) for a key.
@@ -205,24 +249,6 @@ class Redis
205
249
  # @param [String, Array<String>] keys
206
250
  # @return [Integer]
207
251
  def exists(*keys)
208
- if !Redis.exists_returns_integer && keys.size == 1
209
- if Redis.exists_returns_integer.nil?
210
- message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
211
- "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
212
- "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
213
- "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0.0. " \
214
- "(#{::Kernel.caller(1, 1).first})\n"
215
-
216
- ::Redis.deprecate!(message)
217
- end
218
-
219
- exists?(*keys)
220
- else
221
- _exists(*keys)
222
- end
223
- end
224
-
225
- def _exists(*keys)
226
252
  send_command([:exists, *keys])
227
253
  end
228
254
 
@@ -48,7 +48,7 @@ class Redis
48
48
  # @param [String, Symbol] where_destination where to push the element to the source list
49
49
  # e.g. 'LEFT' - to head, 'RIGHT' - to tail
50
50
  # @param [Hash] options
51
- # - `:timeout => Numeric`: timeout in seconds, defaults to no timeout
51
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
52
52
  #
53
53
  # @return [nil, String] the element, or nil when the source key does not exist or the timeout expired
54
54
  #
@@ -142,7 +142,7 @@ class Redis
142
142
  # @param [String, Array<String>] keys one or more keys to perform the
143
143
  # blocking pop on
144
144
  # @param [Hash] options
145
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
145
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
146
146
  #
147
147
  # @return [nil, [String, String]]
148
148
  # - `nil` when the operation timed out
@@ -156,7 +156,7 @@ class Redis
156
156
  # @param [String, Array<String>] keys one or more keys to perform the
157
157
  # blocking pop on
158
158
  # @param [Hash] options
159
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
159
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
160
160
  #
161
161
  # @return [nil, [String, String]]
162
162
  # - `nil` when the operation timed out
@@ -173,12 +173,12 @@ class Redis
173
173
  # @param [String] source source key
174
174
  # @param [String] destination destination key
175
175
  # @param [Hash] options
176
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
176
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
177
177
  #
178
178
  # @return [nil, String]
179
179
  # - `nil` when the operation timed out
180
180
  # - the element was popped and pushed otherwise
181
- def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
181
+ def brpoplpush(source, destination, timeout: 0)
182
182
  command = [:brpoplpush, source, destination, timeout]
183
183
  send_blocking_command(command, timeout)
184
184
  end
@@ -253,20 +253,16 @@ class Redis
253
253
  timeout = if args.last.is_a?(Hash)
254
254
  options = args.pop
255
255
  options[:timeout]
256
- elsif args.last.respond_to?(:to_int)
257
- # Issue deprecation notice in obnoxious mode...
258
- args.pop.to_int
259
256
  end
260
257
 
261
258
  timeout ||= 0
262
-
263
- if args.size > 1
264
- # Issue deprecation notice in obnoxious mode...
259
+ unless timeout.is_a?(Integer) || timeout.is_a?(Float)
260
+ raise ArgumentError, "timeout must be an Integer or Float, got: #{timeout.class}"
265
261
  end
266
262
 
267
- keys = args.flatten
268
-
269
- command = [cmd, keys, timeout]
263
+ args.flatten!(1)
264
+ command = [cmd].concat(args)
265
+ command << timeout
270
266
  send_blocking_command(command, timeout, &blk)
271
267
  end
272
268
 
@@ -9,57 +9,39 @@ 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.
42
32
  def psubscribe(*channels, &block)
43
- synchronize do |_client|
44
- _subscription(:psubscribe, 0, channels, block)
45
- end
33
+ _subscription(:psubscribe, 0, channels, block)
46
34
  end
47
35
 
48
36
  # Listen for messages published to channels matching the given patterns.
49
37
  # Throw a timeout error if there is no messages for a timeout period.
50
38
  def psubscribe_with_timeout(timeout, *channels, &block)
51
- synchronize do |_client|
52
- _subscription(:psubscribe_with_timeout, timeout, channels, block)
53
- end
39
+ _subscription(:psubscribe_with_timeout, timeout, channels, block)
54
40
  end
55
41
 
56
42
  # Stop listening for messages posted to channels matching the given patterns.
57
43
  def punsubscribe(*channels)
58
- synchronize do |client|
59
- raise "Can't unsubscribe if not subscribed." unless subscribed?
60
-
61
- client.punsubscribe(*channels)
62
- end
44
+ _subscription(:punsubscribe, 0, channels, nil)
63
45
  end
64
46
 
65
47
  # Inspect the state of the Pub/Sub subsystem.
@@ -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 << length if length
162
+ send_command(args)
163
163
  end
164
164
 
165
165
  # Internal command used for replication.
@@ -15,40 +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
- send_command([:sadd, key, member]) do |reply|
24
- if member.is_a? Array
25
- # Variadic: return integer
26
- reply
27
- else
28
- # Single argument: return boolean
29
- Boolify.call(reply)
30
- end
31
- end
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))
22
+ end
23
+
24
+ # Add one or more members to a set.
25
+ #
26
+ # @param [String] key
27
+ # @param [String, Array<String>] member one member, or array of members
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)
32
32
  end
33
33
 
34
34
  # Remove one or more members from a set.
35
35
  #
36
36
  # @param [String] key
37
37
  # @param [String, Array<String>] member one member, or array of members
38
- # @return [Boolean, Integer] `Boolean` when a single member is specified,
39
- # holding whether or not removing the member succeeded, or `Integer` when an
40
- # array of members is specified, holding the number of members that were
41
- # successfully removed
42
- def srem(key, member)
43
- send_command([:srem, key, member]) do |reply|
44
- if member.is_a? Array
45
- # Variadic: return integer
46
- reply
47
- else
48
- # Single argument: return boolean
49
- Boolify.call(reply)
50
- end
51
- end
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))
42
+ end
43
+
44
+ # Remove one or more members from a set.
45
+ #
46
+ # @param [String] key
47
+ # @param [String, Array<String>] member one member, or array of members
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)
52
52
  end
53
53
 
54
54
  # Remove and return one or more random member from a set.
@@ -102,7 +102,8 @@ class Redis
102
102
  # @param [String, Array<String>] members
103
103
  # @return [Array<Boolean>]
104
104
  def smismember(key, *members)
105
- send_command([:smismember, key, *members]) do |reply|
105
+ members.flatten!(1)
106
+ send_command([:smismember, key].concat(members)) do |reply|
106
107
  reply.map(&Boolify)
107
108
  end
108
109
  end
@@ -120,7 +121,8 @@ class Redis
120
121
  # @param [String, Array<String>] keys keys pointing to sets to subtract
121
122
  # @return [Array<String>] members in the difference
122
123
  def sdiff(*keys)
123
- send_command([:sdiff, *keys])
124
+ keys.flatten!(1)
125
+ send_command([:sdiff].concat(keys))
124
126
  end
125
127
 
126
128
  # Subtract multiple sets and store the resulting set in a key.
@@ -129,7 +131,8 @@ class Redis
129
131
  # @param [String, Array<String>] keys keys pointing to sets to subtract
130
132
  # @return [Integer] number of elements in the resulting set
131
133
  def sdiffstore(destination, *keys)
132
- send_command([:sdiffstore, destination, *keys])
134
+ keys.flatten!(1)
135
+ send_command([:sdiffstore, destination].concat(keys))
133
136
  end
134
137
 
135
138
  # Intersect multiple sets.
@@ -137,7 +140,8 @@ class Redis
137
140
  # @param [String, Array<String>] keys keys pointing to sets to intersect
138
141
  # @return [Array<String>] members in the intersection
139
142
  def sinter(*keys)
140
- send_command([:sinter, *keys])
143
+ keys.flatten!(1)
144
+ send_command([:sinter].concat(keys))
141
145
  end
142
146
 
143
147
  # Intersect multiple sets and store the resulting set in a key.
@@ -146,7 +150,8 @@ class Redis
146
150
  # @param [String, Array<String>] keys keys pointing to sets to intersect
147
151
  # @return [Integer] number of elements in the resulting set
148
152
  def sinterstore(destination, *keys)
149
- send_command([:sinterstore, destination, *keys])
153
+ keys.flatten!(1)
154
+ send_command([:sinterstore, destination].concat(keys))
150
155
  end
151
156
 
152
157
  # Add multiple sets.
@@ -154,7 +159,8 @@ class Redis
154
159
  # @param [String, Array<String>] keys keys pointing to sets to unify
155
160
  # @return [Array<String>] members in the union
156
161
  def sunion(*keys)
157
- send_command([:sunion, *keys])
162
+ keys.flatten!(1)
163
+ send_command([:sunion].concat(keys))
158
164
  end
159
165
 
160
166
  # Add multiple sets and store the resulting set in a key.
@@ -163,7 +169,8 @@ class Redis
163
169
  # @param [String, Array<String>] keys keys pointing to sets to unify
164
170
  # @return [Integer] number of elements in the resulting set
165
171
  def sunionstore(destination, *keys)
166
- send_command([:sunionstore, destination, *keys])
172
+ keys.flatten!(1)
173
+ send_command([:sunionstore, destination].concat(keys))
167
174
  end
168
175
 
169
176
  # Scan a set
@@ -778,7 +778,8 @@ class Redis
778
778
  private
779
779
 
780
780
  def _zsets_operation(cmd, *keys, weights: nil, aggregate: nil, with_scores: false)
781
- command = [cmd, keys.size, *keys]
781
+ keys.flatten!(1)
782
+ command = [cmd, keys.size].concat(keys)
782
783
 
783
784
  if weights
784
785
  command << "WEIGHTS"
@@ -796,7 +797,8 @@ class Redis
796
797
  end
797
798
 
798
799
  def _zsets_operation_store(cmd, destination, keys, weights: nil, aggregate: nil)
799
- command = [cmd, destination, keys.size, *keys]
800
+ keys.flatten!(1)
801
+ command = [cmd, destination, keys.size].concat(keys)
800
802
 
801
803
  if weights
802
804
  command << "WEIGHTS"
@@ -21,15 +21,12 @@ class Redis
21
21
  # @return [Array<Hash>] information of the consumers if subcommand is `consumers`
22
22
  def xinfo(subcommand, key, group = nil)
23
23
  args = [:xinfo, subcommand, key, group].compact
24
- synchronize do |client|
25
- client.call(args) do |reply|
26
- case subcommand.to_s.downcase
27
- when 'stream' then Hashify.call(reply)
28
- when 'groups', 'consumers' then reply.map { |arr| Hashify.call(arr) }
29
- else reply
30
- end
31
- end
24
+ block = case subcommand.to_s.downcase
25
+ when 'stream' then Hashify
26
+ when 'groups', 'consumers' then proc { |r| r.map(&Hashify) }
32
27
  end
28
+
29
+ send_command(args, &block)
33
30
  end
34
31
 
35
32
  # Add new entry to the stream.
@@ -113,7 +110,7 @@ class Redis
113
110
  def xrange(key, start = '-', range_end = '+', count: nil)
114
111
  args = [:xrange, key, start, range_end]
115
112
  args.concat(['COUNT', count]) if count
116
- synchronize { |client| client.call(args, &HashifyStreamEntries) }
113
+ send_command(args, &HashifyStreamEntries)
117
114
  end
118
115
 
119
116
  # Fetches entries of the stream in descending order.
@@ -334,6 +331,8 @@ class Redis
334
331
  # redis.xpending('mystream', 'mygroup')
335
332
  # @example With range options
336
333
  # redis.xpending('mystream', 'mygroup', '-', '+', 10)
334
+ # @example With range and idle time options
335
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, idle: 9000)
337
336
  # @example With range and consumer options
338
337
  # redis.xpending('mystream', 'mygroup', '-', '+', 10, 'consumer1')
339
338
  #
@@ -344,10 +343,13 @@ class Redis
344
343
  # @param count [Integer] count the number of entries as limit
345
344
  # @param consumer [String] the consumer name
346
345
  #
346
+ # @option opts [Integer] :idle pending message minimum idle time in milliseconds
347
+ #
347
348
  # @return [Hash] the summary of pending entries
348
349
  # @return [Array<Hash>] the pending entries details if options were specified
349
- def xpending(key, group, *args)
350
+ def xpending(key, group, *args, idle: nil)
350
351
  command_args = [:xpending, key, group]
352
+ command_args << 'IDLE' << Integer(idle) if idle
351
353
  case args.size
352
354
  when 0, 3, 4
353
355
  command_args.concat(args)
@@ -202,6 +202,7 @@ class Redis
202
202
  #
203
203
  # @see #mapped_mget
204
204
  def mget(*keys, &blk)
205
+ keys.flatten!(1)
205
206
  send_command([:mget, *keys], &blk)
206
207
  end
207
208