redis 4.7.1 → 5.4.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +93 -0
  3. data/README.md +125 -162
  4. data/lib/redis/client.rb +82 -625
  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 +3 -3
  9. data/lib/redis/commands/hashes.rb +13 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +75 -27
  12. data/lib/redis/commands/lists.rb +73 -23
  13. data/lib/redis/commands/pubsub.rb +34 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +47 -36
  16. data/lib/redis/commands/sorted_sets.rb +128 -18
  17. data/lib/redis/commands/streams.rb +48 -21
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +11 -14
  21. data/lib/redis/distributed.rb +150 -75
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +47 -222
  25. data/lib/redis/subscribe.rb +50 -14
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +76 -182
  28. metadata +10 -57
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -66
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
@@ -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,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.
@@ -60,7 +60,7 @@ class Redis
60
60
  if count.nil?
61
61
  send_command([:spop, key])
62
62
  else
63
- send_command([:spop, key, count])
63
+ send_command([:spop, key, Integer(count)])
64
64
  end
65
65
  end
66
66
 
@@ -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
@@ -177,6 +184,8 @@ class Redis
177
184
  # - `:count => Integer`: return count keys at most per iteration
178
185
  #
179
186
  # @return [String, Array<String>] the next cursor and all found members
187
+ #
188
+ # See the [Redis Server SSCAN documentation](https://redis.io/docs/latest/commands/sscan/) for further details
180
189
  def sscan(key, cursor, **options)
181
190
  _scan(:sscan, cursor, [key], **options)
182
191
  end
@@ -192,6 +201,8 @@ class Redis
192
201
  # - `:count => Integer`: return count keys at most per iteration
193
202
  #
194
203
  # @return [Enumerator] an enumerator for all keys in the set
204
+ #
205
+ # See the [Redis Server SSCAN documentation](https://redis.io/docs/latest/commands/sscan/) for further details
195
206
  def sscan_each(key, **options, &block)
196
207
  return to_enum(:sscan_each, key, **options) unless block_given?
197
208
 
@@ -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"
@@ -313,7 +383,7 @@ class Redis
313
383
 
314
384
  if limit
315
385
  args << "LIMIT"
316
- args.concat(limit)
386
+ args.concat(limit.map { |l| Integer(l) })
317
387
  end
318
388
 
319
389
  if with_scores
@@ -354,7 +424,7 @@ class Redis
354
424
 
355
425
  if limit
356
426
  args << "LIMIT"
357
- args.concat(limit)
427
+ args.concat(limit.map { |l| Integer(l) })
358
428
  end
359
429
 
360
430
  send_command(args)
@@ -372,7 +442,7 @@ class Redis
372
442
  #
373
443
  # @see #zrange
374
444
  def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
375
- args = [:zrevrange, key, start, stop]
445
+ args = [:zrevrange, key, Integer(start), Integer(stop)]
376
446
 
377
447
  if with_scores
378
448
  args << "WITHSCORES"
@@ -384,21 +454,55 @@ class Redis
384
454
 
385
455
  # Determine the index of a member in a sorted set.
386
456
  #
457
+ # @example Retrieve member rank
458
+ # redis.zrank("zset", "a")
459
+ # # => 3
460
+ # @example Retrieve member rank with their score
461
+ # redis.zrank("zset", "a", :with_score => true)
462
+ # # => [3, 32.0]
463
+ #
387
464
  # @param [String] key
388
465
  # @param [String] member
389
- # @return [Integer]
390
- def zrank(key, member)
391
- send_command([:zrank, key, member])
466
+ #
467
+ # @return [Integer, [Integer, Float]]
468
+ # - when `:with_score` is not specified, an Integer
469
+ # - when `:with_score` is specified, a `[rank, score]` pair
470
+ def zrank(key, member, withscore: false, with_score: withscore)
471
+ args = [:zrank, key, member]
472
+
473
+ if with_score
474
+ args << "WITHSCORE"
475
+ block = FloatifyPair
476
+ end
477
+
478
+ send_command(args, &block)
392
479
  end
393
480
 
394
481
  # Determine the index of a member in a sorted set, with scores ordered from
395
482
  # high to low.
396
483
  #
484
+ # @example Retrieve member rank
485
+ # redis.zrevrank("zset", "a")
486
+ # # => 3
487
+ # @example Retrieve member rank with their score
488
+ # redis.zrevrank("zset", "a", :with_score => true)
489
+ # # => [3, 32.0]
490
+ #
397
491
  # @param [String] key
398
492
  # @param [String] member
399
- # @return [Integer]
400
- def zrevrank(key, member)
401
- send_command([:zrevrank, key, member])
493
+ #
494
+ # @return [Integer, [Integer, Float]]
495
+ # - when `:with_score` is not specified, an Integer
496
+ # - when `:with_score` is specified, a `[rank, score]` pair
497
+ def zrevrank(key, member, withscore: false, with_score: withscore)
498
+ args = [:zrevrank, key, member]
499
+
500
+ if with_score
501
+ args << "WITHSCORE"
502
+ block = FloatifyPair
503
+ end
504
+
505
+ send_command(args, &block)
402
506
  end
403
507
 
404
508
  # Remove all members in a sorted set within the given indexes.
@@ -466,7 +570,7 @@ class Redis
466
570
 
467
571
  if limit
468
572
  args << "LIMIT"
469
- args.concat(limit)
573
+ args.concat(limit.map { |l| Integer(l) })
470
574
  end
471
575
 
472
576
  send_command(args)
@@ -488,7 +592,7 @@ class Redis
488
592
 
489
593
  if limit
490
594
  args << "LIMIT"
491
- args.concat(limit)
595
+ args.concat(limit.map { |l| Integer(l) })
492
596
  end
493
597
 
494
598
  send_command(args)
@@ -531,7 +635,7 @@ class Redis
531
635
 
532
636
  if limit
533
637
  args << "LIMIT"
534
- args.concat(limit)
638
+ args.concat(limit.map { |l| Integer(l) })
535
639
  end
536
640
 
537
641
  send_command(args, &block)
@@ -561,7 +665,7 @@ class Redis
561
665
 
562
666
  if limit
563
667
  args << "LIMIT"
564
- args.concat(limit)
668
+ args.concat(limit.map { |l| Integer(l) })
565
669
  end
566
670
 
567
671
  send_command(args, &block)
@@ -747,6 +851,8 @@ class Redis
747
851
  #
748
852
  # @return [String, Array<[String, Float]>] the next cursor and all found
749
853
  # members and scores
854
+ #
855
+ # See the [Redis Server ZSCAN documentation](https://redis.io/docs/latest/commands/zscan/) for further details
750
856
  def zscan(key, cursor, **options)
751
857
  _scan(:zscan, cursor, [key], **options) do |reply|
752
858
  [reply[0], FloatifyPairs.call(reply[1])]
@@ -764,6 +870,8 @@ class Redis
764
870
  # - `:count => Integer`: return count keys at most per iteration
765
871
  #
766
872
  # @return [Enumerator] an enumerator for all found scores and members
873
+ #
874
+ # See the [Redis Server ZSCAN documentation](https://redis.io/docs/latest/commands/zscan/) for further details
767
875
  def zscan_each(key, **options, &block)
768
876
  return to_enum(:zscan_each, key, **options) unless block_given?
769
877
 
@@ -778,7 +886,8 @@ class Redis
778
886
  private
779
887
 
780
888
  def _zsets_operation(cmd, *keys, weights: nil, aggregate: nil, with_scores: false)
781
- command = [cmd, keys.size, *keys]
889
+ keys.flatten!(1)
890
+ command = [cmd, keys.size].concat(keys)
782
891
 
783
892
  if weights
784
893
  command << "WEIGHTS"
@@ -796,7 +905,8 @@ class Redis
796
905
  end
797
906
 
798
907
  def _zsets_operation_store(cmd, destination, keys, weights: nil, aggregate: nil)
799
- command = [cmd, destination, keys.size, *keys]
908
+ keys.flatten!(1)
909
+ command = [cmd, destination, keys.size].concat(keys)
800
910
 
801
911
  if weights
802
912
  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.
@@ -37,26 +34,35 @@ class Redis
37
34
  # @example Without options
38
35
  # redis.xadd('mystream', f1: 'v1', f2: 'v2')
39
36
  # @example With options
40
- # redis.xadd('mystream', { f1: 'v1', f2: 'v2' }, id: '0-0', maxlen: 1000, approximate: true)
37
+ # redis.xadd('mystream', { f1: 'v1', f2: 'v2' }, id: '0-0', maxlen: 1000, approximate: true, nomkstream: true)
41
38
  #
42
39
  # @param key [String] the stream key
43
40
  # @param entry [Hash] one or multiple field-value pairs
44
41
  # @param opts [Hash] several options for `XADD` command
45
42
  #
46
43
  # @option opts [String] :id the entry id, default value is `*`, it means auto generation
47
- # @option opts [Integer] :maxlen max length of entries
48
- # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
44
+ # @option opts [Integer] :maxlen max length of entries to keep
45
+ # @option opts [Integer] :minid min id of entries to keep
46
+ # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen/minid or not
47
+ # @option opts [Boolean] :nomkstream whether to add NOMKSTREAM, default is not to add
49
48
  #
50
49
  # @return [String] the entry id
51
- def xadd(key, entry, approximate: nil, maxlen: nil, id: '*')
50
+ def xadd(key, entry, approximate: nil, maxlen: nil, minid: nil, nomkstream: nil, id: '*')
52
51
  args = [:xadd, key]
52
+ args << 'NOMKSTREAM' if nomkstream
53
53
  if maxlen
54
+ raise ArgumentError, "can't supply both maxlen and minid" if minid
55
+
54
56
  args << "MAXLEN"
55
57
  args << "~" if approximate
56
58
  args << maxlen
59
+ elsif minid
60
+ args << "MINID"
61
+ args << "~" if approximate
62
+ args << minid
57
63
  end
58
64
  args << id
59
- args.concat(entry.to_a.flatten)
65
+ args.concat(entry.flatten)
60
66
  send_command(args)
61
67
  end
62
68
 
@@ -66,14 +72,30 @@ class Redis
66
72
  # redis.xtrim('mystream', 1000)
67
73
  # @example With options
68
74
  # redis.xtrim('mystream', 1000, approximate: true)
69
- #
70
- # @param key [String] the stream key
71
- # @param mexlen [Integer] max length of entries
72
- # @param approximate [Boolean] whether to add `~` modifier of maxlen or not
75
+ # @example With strategy
76
+ # redis.xtrim('mystream', '1-0', strategy: 'MINID')
77
+ #
78
+ # @overload xtrim(key, maxlen, strategy: 'MAXLEN', approximate: true)
79
+ # @param key [String] the stream key
80
+ # @param maxlen [Integer] max length of entries
81
+ # @param strategy [String] the limit strategy, must be MAXLEN
82
+ # @param approximate [Boolean] whether to add `~` modifier of maxlen or not
83
+ # @param limit [Integer] maximum count of entries to be evicted
84
+ # @overload xtrim(key, minid, strategy: 'MINID', approximate: true)
85
+ # @param key [String] the stream key
86
+ # @param minid [String] minimum id of entries
87
+ # @param strategy [String] the limit strategy, must be MINID
88
+ # @param approximate [Boolean] whether to add `~` modifier of minid or not
89
+ # @param limit [Integer] maximum count of entries to be evicted
73
90
  #
74
91
  # @return [Integer] the number of entries actually deleted
75
- def xtrim(key, maxlen, approximate: false)
76
- args = [:xtrim, key, 'MAXLEN', (approximate ? '~' : nil), maxlen].compact
92
+ def xtrim(key, len_or_id, strategy: 'MAXLEN', approximate: false, limit: nil)
93
+ strategy = strategy.to_s.upcase
94
+
95
+ args = [:xtrim, key, strategy]
96
+ args << '~' if approximate
97
+ args << len_or_id
98
+ args.concat(['LIMIT', limit]) if limit
77
99
  send_command(args)
78
100
  end
79
101
 
@@ -113,7 +135,7 @@ class Redis
113
135
  def xrange(key, start = '-', range_end = '+', count: nil)
114
136
  args = [:xrange, key, start, range_end]
115
137
  args.concat(['COUNT', count]) if count
116
- synchronize { |client| client.call(args, &HashifyStreamEntries) }
138
+ send_command(args, &HashifyStreamEntries)
117
139
  end
118
140
 
119
141
  # Fetches entries of the stream in descending order.
@@ -334,6 +356,8 @@ class Redis
334
356
  # redis.xpending('mystream', 'mygroup')
335
357
  # @example With range options
336
358
  # redis.xpending('mystream', 'mygroup', '-', '+', 10)
359
+ # @example With range and idle time options
360
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, idle: 9000)
337
361
  # @example With range and consumer options
338
362
  # redis.xpending('mystream', 'mygroup', '-', '+', 10, 'consumer1')
339
363
  #
@@ -344,10 +368,13 @@ class Redis
344
368
  # @param count [Integer] count the number of entries as limit
345
369
  # @param consumer [String] the consumer name
346
370
  #
371
+ # @option opts [Integer] :idle pending message minimum idle time in milliseconds
372
+ #
347
373
  # @return [Hash] the summary of pending entries
348
374
  # @return [Array<Hash>] the pending entries details if options were specified
349
- def xpending(key, group, *args)
375
+ def xpending(key, group, *args, idle: nil)
350
376
  command_args = [:xpending, key, group]
377
+ command_args << 'IDLE' << Integer(idle) if idle
351
378
  case args.size
352
379
  when 0, 3, 4
353
380
  command_args.concat(args)