redis 3.3.5 → 5.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +290 -2
  3. data/README.md +146 -146
  4. data/lib/redis/client.rb +79 -541
  5. data/lib/redis/commands/bitmaps.rb +66 -0
  6. data/lib/redis/commands/cluster.rb +28 -0
  7. data/lib/redis/commands/connection.rb +53 -0
  8. data/lib/redis/commands/geo.rb +84 -0
  9. data/lib/redis/commands/hashes.rb +254 -0
  10. data/lib/redis/commands/hyper_log_log.rb +37 -0
  11. data/lib/redis/commands/keys.rb +437 -0
  12. data/lib/redis/commands/lists.rb +339 -0
  13. data/lib/redis/commands/pubsub.rb +54 -0
  14. data/lib/redis/commands/scripting.rb +114 -0
  15. data/lib/redis/commands/server.rb +188 -0
  16. data/lib/redis/commands/sets.rb +214 -0
  17. data/lib/redis/commands/sorted_sets.rb +884 -0
  18. data/lib/redis/commands/streams.rb +402 -0
  19. data/lib/redis/commands/strings.rb +314 -0
  20. data/lib/redis/commands/transactions.rb +115 -0
  21. data/lib/redis/commands.rb +237 -0
  22. data/lib/redis/distributed.rb +328 -108
  23. data/lib/redis/errors.rb +23 -1
  24. data/lib/redis/hash_ring.rb +36 -79
  25. data/lib/redis/pipeline.rb +69 -83
  26. data/lib/redis/subscribe.rb +26 -19
  27. data/lib/redis/version.rb +3 -1
  28. data/lib/redis.rb +115 -2695
  29. metadata +38 -218
  30. data/.gitignore +0 -16
  31. data/.travis/Gemfile +0 -11
  32. data/.travis.yml +0 -89
  33. data/.yardopts +0 -3
  34. data/Gemfile +0 -4
  35. data/Rakefile +0 -87
  36. data/benchmarking/logging.rb +0 -71
  37. data/benchmarking/pipeline.rb +0 -51
  38. data/benchmarking/speed.rb +0 -21
  39. data/benchmarking/suite.rb +0 -24
  40. data/benchmarking/worker.rb +0 -71
  41. data/examples/basic.rb +0 -15
  42. data/examples/consistency.rb +0 -114
  43. data/examples/dist_redis.rb +0 -43
  44. data/examples/incr-decr.rb +0 -17
  45. data/examples/list.rb +0 -26
  46. data/examples/pubsub.rb +0 -37
  47. data/examples/sentinel/sentinel.conf +0 -9
  48. data/examples/sentinel/start +0 -49
  49. data/examples/sentinel.rb +0 -41
  50. data/examples/sets.rb +0 -36
  51. data/examples/unicorn/config.ru +0 -3
  52. data/examples/unicorn/unicorn.rb +0 -20
  53. data/lib/redis/connection/command_helper.rb +0 -44
  54. data/lib/redis/connection/hiredis.rb +0 -66
  55. data/lib/redis/connection/registry.rb +0 -12
  56. data/lib/redis/connection/ruby.rb +0 -429
  57. data/lib/redis/connection/synchrony.rb +0 -133
  58. data/lib/redis/connection.rb +0 -9
  59. data/redis.gemspec +0 -44
  60. data/test/bitpos_test.rb +0 -69
  61. data/test/blocking_commands_test.rb +0 -42
  62. data/test/client_test.rb +0 -59
  63. data/test/command_map_test.rb +0 -30
  64. data/test/commands_on_hashes_test.rb +0 -21
  65. data/test/commands_on_hyper_log_log_test.rb +0 -21
  66. data/test/commands_on_lists_test.rb +0 -20
  67. data/test/commands_on_sets_test.rb +0 -77
  68. data/test/commands_on_sorted_sets_test.rb +0 -137
  69. data/test/commands_on_strings_test.rb +0 -101
  70. data/test/commands_on_value_types_test.rb +0 -133
  71. data/test/connection_handling_test.rb +0 -277
  72. data/test/connection_test.rb +0 -57
  73. data/test/db/.gitkeep +0 -0
  74. data/test/distributed_blocking_commands_test.rb +0 -46
  75. data/test/distributed_commands_on_hashes_test.rb +0 -10
  76. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  77. data/test/distributed_commands_on_lists_test.rb +0 -22
  78. data/test/distributed_commands_on_sets_test.rb +0 -83
  79. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  80. data/test/distributed_commands_on_strings_test.rb +0 -59
  81. data/test/distributed_commands_on_value_types_test.rb +0 -95
  82. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  83. data/test/distributed_connection_handling_test.rb +0 -23
  84. data/test/distributed_internals_test.rb +0 -79
  85. data/test/distributed_key_tags_test.rb +0 -52
  86. data/test/distributed_persistence_control_commands_test.rb +0 -26
  87. data/test/distributed_publish_subscribe_test.rb +0 -92
  88. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  89. data/test/distributed_scripting_test.rb +0 -102
  90. data/test/distributed_sorting_test.rb +0 -20
  91. data/test/distributed_test.rb +0 -58
  92. data/test/distributed_transactions_test.rb +0 -32
  93. data/test/encoding_test.rb +0 -18
  94. data/test/error_replies_test.rb +0 -59
  95. data/test/fork_safety_test.rb +0 -65
  96. data/test/helper.rb +0 -232
  97. data/test/helper_test.rb +0 -24
  98. data/test/internals_test.rb +0 -417
  99. data/test/lint/blocking_commands.rb +0 -150
  100. data/test/lint/hashes.rb +0 -162
  101. data/test/lint/hyper_log_log.rb +0 -60
  102. data/test/lint/lists.rb +0 -143
  103. data/test/lint/sets.rb +0 -140
  104. data/test/lint/sorted_sets.rb +0 -316
  105. data/test/lint/strings.rb +0 -260
  106. data/test/lint/value_types.rb +0 -122
  107. data/test/persistence_control_commands_test.rb +0 -26
  108. data/test/pipelining_commands_test.rb +0 -242
  109. data/test/publish_subscribe_test.rb +0 -282
  110. data/test/remote_server_control_commands_test.rb +0 -118
  111. data/test/scanning_test.rb +0 -413
  112. data/test/scripting_test.rb +0 -78
  113. data/test/sentinel_command_test.rb +0 -80
  114. data/test/sentinel_test.rb +0 -255
  115. data/test/sorting_test.rb +0 -59
  116. data/test/ssl_test.rb +0 -73
  117. data/test/support/connection/hiredis.rb +0 -1
  118. data/test/support/connection/ruby.rb +0 -1
  119. data/test/support/connection/synchrony.rb +0 -17
  120. data/test/support/redis_mock.rb +0 -130
  121. data/test/support/ssl/gen_certs.sh +0 -31
  122. data/test/support/ssl/trusted-ca.crt +0 -25
  123. data/test/support/ssl/trusted-ca.key +0 -27
  124. data/test/support/ssl/trusted-cert.crt +0 -81
  125. data/test/support/ssl/trusted-cert.key +0 -28
  126. data/test/support/ssl/untrusted-ca.crt +0 -26
  127. data/test/support/ssl/untrusted-ca.key +0 -27
  128. data/test/support/ssl/untrusted-cert.crt +0 -82
  129. data/test/support/ssl/untrusted-cert.key +0 -28
  130. data/test/support/wire/synchrony.rb +0 -24
  131. data/test/support/wire/thread.rb +0 -5
  132. data/test/synchrony_driver.rb +0 -88
  133. data/test/test.conf.erb +0 -9
  134. data/test/thread_safety_test.rb +0 -62
  135. data/test/transactions_test.rb +0 -264
  136. data/test/unknown_commands_test.rb +0 -14
  137. data/test/url_param_test.rb +0 -138
@@ -0,0 +1,402 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Streams
6
+ # Returns the stream information each subcommand.
7
+ #
8
+ # @example stream
9
+ # redis.xinfo(:stream, 'mystream')
10
+ # @example groups
11
+ # redis.xinfo(:groups, 'mystream')
12
+ # @example consumers
13
+ # redis.xinfo(:consumers, 'mystream', 'mygroup')
14
+ #
15
+ # @param subcommand [String] e.g. `stream` `groups` `consumers`
16
+ # @param key [String] the stream key
17
+ # @param group [String] the consumer group name, required if subcommand is `consumers`
18
+ #
19
+ # @return [Hash] information of the stream if subcommand is `stream`
20
+ # @return [Array<Hash>] information of the consumer groups if subcommand is `groups`
21
+ # @return [Array<Hash>] information of the consumers if subcommand is `consumers`
22
+ def xinfo(subcommand, key, group = nil)
23
+ args = [:xinfo, subcommand, key, group].compact
24
+ block = case subcommand.to_s.downcase
25
+ when 'stream' then Hashify
26
+ when 'groups', 'consumers' then proc { |r| r.map(&Hashify) }
27
+ end
28
+
29
+ send_command(args, &block)
30
+ end
31
+
32
+ # Add new entry to the stream.
33
+ #
34
+ # @example Without options
35
+ # redis.xadd('mystream', f1: 'v1', f2: 'v2')
36
+ # @example With options
37
+ # redis.xadd('mystream', { f1: 'v1', f2: 'v2' }, id: '0-0', maxlen: 1000, approximate: true, nomkstream: true)
38
+ #
39
+ # @param key [String] the stream key
40
+ # @param entry [Hash] one or multiple field-value pairs
41
+ # @param opts [Hash] several options for `XADD` command
42
+ #
43
+ # @option opts [String] :id the entry id, default value is `*`, it means auto generation
44
+ # @option opts [Integer] :maxlen max length of entries
45
+ # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
46
+ # @option opts [Boolean] :nomkstream whether to add NOMKSTREAM, default is not to add
47
+ #
48
+ # @return [String] the entry id
49
+ def xadd(key, entry, approximate: nil, maxlen: nil, nomkstream: nil, id: '*')
50
+ args = [:xadd, key]
51
+ args << 'NOMKSTREAM' if nomkstream
52
+ if maxlen
53
+ args << "MAXLEN"
54
+ args << "~" if approximate
55
+ args << maxlen
56
+ end
57
+ args << id
58
+ args.concat(entry.flatten)
59
+ send_command(args)
60
+ end
61
+
62
+ # Trims older entries of the stream if needed.
63
+ #
64
+ # @example Without options
65
+ # redis.xtrim('mystream', 1000)
66
+ # @example With options
67
+ # redis.xtrim('mystream', 1000, approximate: true)
68
+ # @example With strategy
69
+ # redis.xtrim('mystream', '1-0', strategy: 'MINID')
70
+ #
71
+ # @overload xtrim(key, maxlen, strategy: 'MAXLEN', approximate: true)
72
+ # @param key [String] the stream key
73
+ # @param maxlen [Integer] max length of entries
74
+ # @param strategy [String] the limit strategy, must be MAXLEN
75
+ # @param approximate [Boolean] whether to add `~` modifier of maxlen or not
76
+ # @param limit [Integer] maximum count of entries to be evicted
77
+ # @overload xtrim(key, minid, strategy: 'MINID', approximate: true)
78
+ # @param key [String] the stream key
79
+ # @param minid [String] minimum id of entries
80
+ # @param strategy [String] the limit strategy, must be MINID
81
+ # @param approximate [Boolean] whether to add `~` modifier of minid or not
82
+ # @param limit [Integer] maximum count of entries to be evicted
83
+ #
84
+ # @return [Integer] the number of entries actually deleted
85
+ def xtrim(key, len_or_id, strategy: 'MAXLEN', approximate: false, limit: nil)
86
+ strategy = strategy.to_s.upcase
87
+
88
+ args = [:xtrim, key, strategy]
89
+ args << '~' if approximate
90
+ args << len_or_id
91
+ args.concat(['LIMIT', limit]) if limit
92
+ send_command(args)
93
+ end
94
+
95
+ # Delete entries by entry ids.
96
+ #
97
+ # @example With splatted entry ids
98
+ # redis.xdel('mystream', '0-1', '0-2')
99
+ # @example With arrayed entry ids
100
+ # redis.xdel('mystream', ['0-1', '0-2'])
101
+ #
102
+ # @param key [String] the stream key
103
+ # @param ids [Array<String>] one or multiple entry ids
104
+ #
105
+ # @return [Integer] the number of entries actually deleted
106
+ def xdel(key, *ids)
107
+ args = [:xdel, key].concat(ids.flatten)
108
+ send_command(args)
109
+ end
110
+
111
+ # Fetches entries of the stream in ascending order.
112
+ #
113
+ # @example Without options
114
+ # redis.xrange('mystream')
115
+ # @example With a specific start
116
+ # redis.xrange('mystream', '0-1')
117
+ # @example With a specific start and end
118
+ # redis.xrange('mystream', '0-1', '0-3')
119
+ # @example With count options
120
+ # redis.xrange('mystream', count: 10)
121
+ #
122
+ # @param key [String] the stream key
123
+ # @param start [String] first entry id of range, default value is `-`
124
+ # @param end [String] last entry id of range, default value is `+`
125
+ # @param count [Integer] the number of entries as limit
126
+ #
127
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
128
+ def xrange(key, start = '-', range_end = '+', count: nil)
129
+ args = [:xrange, key, start, range_end]
130
+ args.concat(['COUNT', count]) if count
131
+ send_command(args, &HashifyStreamEntries)
132
+ end
133
+
134
+ # Fetches entries of the stream in descending order.
135
+ #
136
+ # @example Without options
137
+ # redis.xrevrange('mystream')
138
+ # @example With a specific end
139
+ # redis.xrevrange('mystream', '0-3')
140
+ # @example With a specific end and start
141
+ # redis.xrevrange('mystream', '0-3', '0-1')
142
+ # @example With count options
143
+ # redis.xrevrange('mystream', count: 10)
144
+ #
145
+ # @param key [String] the stream key
146
+ # @param end [String] first entry id of range, default value is `+`
147
+ # @param start [String] last entry id of range, default value is `-`
148
+ # @params count [Integer] the number of entries as limit
149
+ #
150
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
151
+ def xrevrange(key, range_end = '+', start = '-', count: nil)
152
+ args = [:xrevrange, key, range_end, start]
153
+ args.concat(['COUNT', count]) if count
154
+ send_command(args, &HashifyStreamEntries)
155
+ end
156
+
157
+ # Returns the number of entries inside a stream.
158
+ #
159
+ # @example With key
160
+ # redis.xlen('mystream')
161
+ #
162
+ # @param key [String] the stream key
163
+ #
164
+ # @return [Integer] the number of entries
165
+ def xlen(key)
166
+ send_command([:xlen, key])
167
+ end
168
+
169
+ # Fetches entries from one or multiple streams. Optionally blocking.
170
+ #
171
+ # @example With a key
172
+ # redis.xread('mystream', '0-0')
173
+ # @example With multiple keys
174
+ # redis.xread(%w[mystream1 mystream2], %w[0-0 0-0])
175
+ # @example With count option
176
+ # redis.xread('mystream', '0-0', count: 2)
177
+ # @example With block option
178
+ # redis.xread('mystream', '$', block: 1000)
179
+ #
180
+ # @param keys [Array<String>] one or multiple stream keys
181
+ # @param ids [Array<String>] one or multiple entry ids
182
+ # @param count [Integer] the number of entries as limit per stream
183
+ # @param block [Integer] the number of milliseconds as blocking timeout
184
+ #
185
+ # @return [Hash{String => Hash{String => Hash}}] the entries
186
+ def xread(keys, ids, count: nil, block: nil)
187
+ args = [:xread]
188
+ args << 'COUNT' << count if count
189
+ args << 'BLOCK' << block.to_i if block
190
+ _xread(args, keys, ids, block)
191
+ end
192
+
193
+ # Manages the consumer group of the stream.
194
+ #
195
+ # @example With `create` subcommand
196
+ # redis.xgroup(:create, 'mystream', 'mygroup', '$')
197
+ # @example With `setid` subcommand
198
+ # redis.xgroup(:setid, 'mystream', 'mygroup', '$')
199
+ # @example With `destroy` subcommand
200
+ # redis.xgroup(:destroy, 'mystream', 'mygroup')
201
+ # @example With `delconsumer` subcommand
202
+ # redis.xgroup(:delconsumer, 'mystream', 'mygroup', 'consumer1')
203
+ #
204
+ # @param subcommand [String] `create` `setid` `destroy` `delconsumer`
205
+ # @param key [String] the stream key
206
+ # @param group [String] the consumer group name
207
+ # @param id_or_consumer [String]
208
+ # * the entry id or `$`, required if subcommand is `create` or `setid`
209
+ # * the consumer name, required if subcommand is `delconsumer`
210
+ # @param mkstream [Boolean] whether to create an empty stream automatically or not
211
+ #
212
+ # @return [String] `OK` if subcommand is `create` or `setid`
213
+ # @return [Integer] effected count if subcommand is `destroy` or `delconsumer`
214
+ def xgroup(subcommand, key, group, id_or_consumer = nil, mkstream: false)
215
+ args = [:xgroup, subcommand, key, group, id_or_consumer, (mkstream ? 'MKSTREAM' : nil)].compact
216
+ send_command(args)
217
+ end
218
+
219
+ # Fetches a subset of the entries from one or multiple streams related with the consumer group.
220
+ # Optionally blocking.
221
+ #
222
+ # @example With a key
223
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>')
224
+ # @example With multiple keys
225
+ # redis.xreadgroup('mygroup', 'consumer1', %w[mystream1 mystream2], %w[> >])
226
+ # @example With count option
227
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', count: 2)
228
+ # @example With block option
229
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', block: 1000)
230
+ # @example With noack option
231
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', noack: true)
232
+ #
233
+ # @param group [String] the consumer group name
234
+ # @param consumer [String] the consumer name
235
+ # @param keys [Array<String>] one or multiple stream keys
236
+ # @param ids [Array<String>] one or multiple entry ids
237
+ # @param opts [Hash] several options for `XREADGROUP` command
238
+ #
239
+ # @option opts [Integer] :count the number of entries as limit
240
+ # @option opts [Integer] :block the number of milliseconds as blocking timeout
241
+ # @option opts [Boolean] :noack whether message loss is acceptable or not
242
+ #
243
+ # @return [Hash{String => Hash{String => Hash}}] the entries
244
+ def xreadgroup(group, consumer, keys, ids, count: nil, block: nil, noack: nil)
245
+ args = [:xreadgroup, 'GROUP', group, consumer]
246
+ args << 'COUNT' << count if count
247
+ args << 'BLOCK' << block.to_i if block
248
+ args << 'NOACK' if noack
249
+ _xread(args, keys, ids, block)
250
+ end
251
+
252
+ # Removes one or multiple entries from the pending entries list of a stream consumer group.
253
+ #
254
+ # @example With a entry id
255
+ # redis.xack('mystream', 'mygroup', '1526569495631-0')
256
+ # @example With splatted entry ids
257
+ # redis.xack('mystream', 'mygroup', '0-1', '0-2')
258
+ # @example With arrayed entry ids
259
+ # redis.xack('mystream', 'mygroup', %w[0-1 0-2])
260
+ #
261
+ # @param key [String] the stream key
262
+ # @param group [String] the consumer group name
263
+ # @param ids [Array<String>] one or multiple entry ids
264
+ #
265
+ # @return [Integer] the number of entries successfully acknowledged
266
+ def xack(key, group, *ids)
267
+ args = [:xack, key, group].concat(ids.flatten)
268
+ send_command(args)
269
+ end
270
+
271
+ # Changes the ownership of a pending entry
272
+ #
273
+ # @example With splatted entry ids
274
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-1', '0-2')
275
+ # @example With arrayed entry ids
276
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2])
277
+ # @example With idle option
278
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], idle: 1000)
279
+ # @example With time option
280
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], time: 1542866959000)
281
+ # @example With retrycount option
282
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], retrycount: 10)
283
+ # @example With force option
284
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], force: true)
285
+ # @example With justid option
286
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], justid: true)
287
+ #
288
+ # @param key [String] the stream key
289
+ # @param group [String] the consumer group name
290
+ # @param consumer [String] the consumer name
291
+ # @param min_idle_time [Integer] the number of milliseconds
292
+ # @param ids [Array<String>] one or multiple entry ids
293
+ # @param opts [Hash] several options for `XCLAIM` command
294
+ #
295
+ # @option opts [Integer] :idle the number of milliseconds as last time it was delivered of the entry
296
+ # @option opts [Integer] :time the number of milliseconds as a specific Unix Epoch time
297
+ # @option opts [Integer] :retrycount the number of retry counter
298
+ # @option opts [Boolean] :force whether to create the pending entry to the pending entries list or not
299
+ # @option opts [Boolean] :justid whether to fetch just an array of entry ids or not
300
+ #
301
+ # @return [Hash{String => Hash}] the entries successfully claimed
302
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
303
+ def xclaim(key, group, consumer, min_idle_time, *ids, **opts)
304
+ args = [:xclaim, key, group, consumer, min_idle_time].concat(ids.flatten)
305
+ args.concat(['IDLE', opts[:idle].to_i]) if opts[:idle]
306
+ args.concat(['TIME', opts[:time].to_i]) if opts[:time]
307
+ args.concat(['RETRYCOUNT', opts[:retrycount]]) if opts[:retrycount]
308
+ args << 'FORCE' if opts[:force]
309
+ args << 'JUSTID' if opts[:justid]
310
+ blk = opts[:justid] ? Noop : HashifyStreamEntries
311
+ send_command(args, &blk)
312
+ end
313
+
314
+ # Transfers ownership of pending stream entries that match the specified criteria.
315
+ #
316
+ # @example Claim next pending message stuck > 5 minutes and mark as retry
317
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0')
318
+ # @example Claim 50 next pending messages stuck > 5 minutes and mark as retry
319
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', count: 50)
320
+ # @example Claim next pending message stuck > 5 minutes and don't mark as retry
321
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', justid: true)
322
+ # @example Claim next pending message after this id stuck > 5 minutes and mark as retry
323
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '1641321233-0')
324
+ #
325
+ # @param key [String] the stream key
326
+ # @param group [String] the consumer group name
327
+ # @param consumer [String] the consumer name
328
+ # @param min_idle_time [Integer] the number of milliseconds
329
+ # @param start [String] entry id to start scanning from or 0-0 for everything
330
+ # @param count [Integer] number of messages to claim (default 1)
331
+ # @param justid [Boolean] whether to fetch just an array of entry ids or not.
332
+ # Does not increment retry count when true
333
+ #
334
+ # @return [Hash{String => Hash}] the entries successfully claimed
335
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
336
+ def xautoclaim(key, group, consumer, min_idle_time, start, count: nil, justid: false)
337
+ args = [:xautoclaim, key, group, consumer, min_idle_time, start]
338
+ if count
339
+ args << 'COUNT' << count.to_s
340
+ end
341
+ args << 'JUSTID' if justid
342
+ blk = justid ? HashifyStreamAutoclaimJustId : HashifyStreamAutoclaim
343
+ send_command(args, &blk)
344
+ end
345
+
346
+ # Fetches not acknowledging pending entries
347
+ #
348
+ # @example With key and group
349
+ # redis.xpending('mystream', 'mygroup')
350
+ # @example With range options
351
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10)
352
+ # @example With range and idle time options
353
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, idle: 9000)
354
+ # @example With range and consumer options
355
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, 'consumer1')
356
+ #
357
+ # @param key [String] the stream key
358
+ # @param group [String] the consumer group name
359
+ # @param start [String] start first entry id of range
360
+ # @param end [String] end last entry id of range
361
+ # @param count [Integer] count the number of entries as limit
362
+ # @param consumer [String] the consumer name
363
+ #
364
+ # @option opts [Integer] :idle pending message minimum idle time in milliseconds
365
+ #
366
+ # @return [Hash] the summary of pending entries
367
+ # @return [Array<Hash>] the pending entries details if options were specified
368
+ def xpending(key, group, *args, idle: nil)
369
+ command_args = [:xpending, key, group]
370
+ command_args << 'IDLE' << Integer(idle) if idle
371
+ case args.size
372
+ when 0, 3, 4
373
+ command_args.concat(args)
374
+ else
375
+ raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 2, 5 or 6)"
376
+ end
377
+
378
+ summary_needed = args.empty?
379
+ blk = summary_needed ? HashifyStreamPendings : HashifyStreamPendingDetails
380
+ send_command(command_args, &blk)
381
+ end
382
+
383
+ private
384
+
385
+ def _xread(args, keys, ids, blocking_timeout_msec)
386
+ keys = keys.is_a?(Array) ? keys : [keys]
387
+ ids = ids.is_a?(Array) ? ids : [ids]
388
+ args << 'STREAMS'
389
+ args.concat(keys)
390
+ args.concat(ids)
391
+
392
+ if blocking_timeout_msec.nil?
393
+ send_command(args, &HashifyStreams)
394
+ elsif blocking_timeout_msec.to_f.zero?
395
+ send_blocking_command(args, 0, &HashifyStreams)
396
+ else
397
+ send_blocking_command(args, blocking_timeout_msec.to_f / 1_000, &HashifyStreams)
398
+ end
399
+ end
400
+ end
401
+ end
402
+ end