ultra-smart-kit 0.0.1

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.
@@ -0,0 +1,409 @@
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 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
48
+ #
49
+ # @return [String] the entry id
50
+ def xadd(key, entry, approximate: nil, maxlen: nil, minid: nil, nomkstream: nil, id: '*')
51
+ args = [:xadd, key]
52
+ args << 'NOMKSTREAM' if nomkstream
53
+ if maxlen
54
+ raise ArgumentError, "can't supply both maxlen and minid" if minid
55
+
56
+ args << "MAXLEN"
57
+ args << "~" if approximate
58
+ args << maxlen
59
+ elsif minid
60
+ args << "MINID"
61
+ args << "~" if approximate
62
+ args << minid
63
+ end
64
+ args << id
65
+ args.concat(entry.flatten)
66
+ send_command(args)
67
+ end
68
+
69
+ # Trims older entries of the stream if needed.
70
+ #
71
+ # @example Without options
72
+ # redis.xtrim('mystream', 1000)
73
+ # @example With options
74
+ # redis.xtrim('mystream', 1000, approximate: true)
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
90
+ #
91
+ # @return [Integer] the number of entries actually deleted
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
99
+ send_command(args)
100
+ end
101
+
102
+ # Delete entries by entry ids.
103
+ #
104
+ # @example With splatted entry ids
105
+ # redis.xdel('mystream', '0-1', '0-2')
106
+ # @example With arrayed entry ids
107
+ # redis.xdel('mystream', ['0-1', '0-2'])
108
+ #
109
+ # @param key [String] the stream key
110
+ # @param ids [Array<String>] one or multiple entry ids
111
+ #
112
+ # @return [Integer] the number of entries actually deleted
113
+ def xdel(key, *ids)
114
+ args = [:xdel, key].concat(ids.flatten)
115
+ send_command(args)
116
+ end
117
+
118
+ # Fetches entries of the stream in ascending order.
119
+ #
120
+ # @example Without options
121
+ # redis.xrange('mystream')
122
+ # @example With a specific start
123
+ # redis.xrange('mystream', '0-1')
124
+ # @example With a specific start and end
125
+ # redis.xrange('mystream', '0-1', '0-3')
126
+ # @example With count options
127
+ # redis.xrange('mystream', count: 10)
128
+ #
129
+ # @param key [String] the stream key
130
+ # @param start [String] first entry id of range, default value is `-`
131
+ # @param end [String] last entry id of range, default value is `+`
132
+ # @param count [Integer] the number of entries as limit
133
+ #
134
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
135
+ def xrange(key, start = '-', range_end = '+', count: nil)
136
+ args = [:xrange, key, start, range_end]
137
+ args.concat(['COUNT', count]) if count
138
+ send_command(args, &HashifyStreamEntries)
139
+ end
140
+
141
+ # Fetches entries of the stream in descending order.
142
+ #
143
+ # @example Without options
144
+ # redis.xrevrange('mystream')
145
+ # @example With a specific end
146
+ # redis.xrevrange('mystream', '0-3')
147
+ # @example With a specific end and start
148
+ # redis.xrevrange('mystream', '0-3', '0-1')
149
+ # @example With count options
150
+ # redis.xrevrange('mystream', count: 10)
151
+ #
152
+ # @param key [String] the stream key
153
+ # @param end [String] first entry id of range, default value is `+`
154
+ # @param start [String] last entry id of range, default value is `-`
155
+ # @params count [Integer] the number of entries as limit
156
+ #
157
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
158
+ def xrevrange(key, range_end = '+', start = '-', count: nil)
159
+ args = [:xrevrange, key, range_end, start]
160
+ args.concat(['COUNT', count]) if count
161
+ send_command(args, &HashifyStreamEntries)
162
+ end
163
+
164
+ # Returns the number of entries inside a stream.
165
+ #
166
+ # @example With key
167
+ # redis.xlen('mystream')
168
+ #
169
+ # @param key [String] the stream key
170
+ #
171
+ # @return [Integer] the number of entries
172
+ def xlen(key)
173
+ send_command([:xlen, key])
174
+ end
175
+
176
+ # Fetches entries from one or multiple streams. Optionally blocking.
177
+ #
178
+ # @example With a key
179
+ # redis.xread('mystream', '0-0')
180
+ # @example With multiple keys
181
+ # redis.xread(%w[mystream1 mystream2], %w[0-0 0-0])
182
+ # @example With count option
183
+ # redis.xread('mystream', '0-0', count: 2)
184
+ # @example With block option
185
+ # redis.xread('mystream', '$', block: 1000)
186
+ #
187
+ # @param keys [Array<String>] one or multiple stream keys
188
+ # @param ids [Array<String>] one or multiple entry ids
189
+ # @param count [Integer] the number of entries as limit per stream
190
+ # @param block [Integer] the number of milliseconds as blocking timeout
191
+ #
192
+ # @return [Hash{String => Hash{String => Hash}}] the entries
193
+ def xread(keys, ids, count: nil, block: nil)
194
+ args = [:xread]
195
+ args << 'COUNT' << count if count
196
+ args << 'BLOCK' << block.to_i if block
197
+ _xread(args, keys, ids, block)
198
+ end
199
+
200
+ # Manages the consumer group of the stream.
201
+ #
202
+ # @example With `create` subcommand
203
+ # redis.xgroup(:create, 'mystream', 'mygroup', '$')
204
+ # @example With `setid` subcommand
205
+ # redis.xgroup(:setid, 'mystream', 'mygroup', '$')
206
+ # @example With `destroy` subcommand
207
+ # redis.xgroup(:destroy, 'mystream', 'mygroup')
208
+ # @example With `delconsumer` subcommand
209
+ # redis.xgroup(:delconsumer, 'mystream', 'mygroup', 'consumer1')
210
+ #
211
+ # @param subcommand [String] `create` `setid` `destroy` `delconsumer`
212
+ # @param key [String] the stream key
213
+ # @param group [String] the consumer group name
214
+ # @param id_or_consumer [String]
215
+ # * the entry id or `$`, required if subcommand is `create` or `setid`
216
+ # * the consumer name, required if subcommand is `delconsumer`
217
+ # @param mkstream [Boolean] whether to create an empty stream automatically or not
218
+ #
219
+ # @return [String] `OK` if subcommand is `create` or `setid`
220
+ # @return [Integer] effected count if subcommand is `destroy` or `delconsumer`
221
+ def xgroup(subcommand, key, group, id_or_consumer = nil, mkstream: false)
222
+ args = [:xgroup, subcommand, key, group, id_or_consumer, (mkstream ? 'MKSTREAM' : nil)].compact
223
+ send_command(args)
224
+ end
225
+
226
+ # Fetches a subset of the entries from one or multiple streams related with the consumer group.
227
+ # Optionally blocking.
228
+ #
229
+ # @example With a key
230
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>')
231
+ # @example With multiple keys
232
+ # redis.xreadgroup('mygroup', 'consumer1', %w[mystream1 mystream2], %w[> >])
233
+ # @example With count option
234
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', count: 2)
235
+ # @example With block option
236
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', block: 1000)
237
+ # @example With noack option
238
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', noack: true)
239
+ #
240
+ # @param group [String] the consumer group name
241
+ # @param consumer [String] the consumer name
242
+ # @param keys [Array<String>] one or multiple stream keys
243
+ # @param ids [Array<String>] one or multiple entry ids
244
+ # @param opts [Hash] several options for `XREADGROUP` command
245
+ #
246
+ # @option opts [Integer] :count the number of entries as limit
247
+ # @option opts [Integer] :block the number of milliseconds as blocking timeout
248
+ # @option opts [Boolean] :noack whether message loss is acceptable or not
249
+ #
250
+ # @return [Hash{String => Hash{String => Hash}}] the entries
251
+ def xreadgroup(group, consumer, keys, ids, count: nil, block: nil, noack: nil)
252
+ args = [:xreadgroup, 'GROUP', group, consumer]
253
+ args << 'COUNT' << count if count
254
+ args << 'BLOCK' << block.to_i if block
255
+ args << 'NOACK' if noack
256
+ _xread(args, keys, ids, block)
257
+ end
258
+
259
+ # Removes one or multiple entries from the pending entries list of a stream consumer group.
260
+ #
261
+ # @example With a entry id
262
+ # redis.xack('mystream', 'mygroup', '1526569495631-0')
263
+ # @example With splatted entry ids
264
+ # redis.xack('mystream', 'mygroup', '0-1', '0-2')
265
+ # @example With arrayed entry ids
266
+ # redis.xack('mystream', 'mygroup', %w[0-1 0-2])
267
+ #
268
+ # @param key [String] the stream key
269
+ # @param group [String] the consumer group name
270
+ # @param ids [Array<String>] one or multiple entry ids
271
+ #
272
+ # @return [Integer] the number of entries successfully acknowledged
273
+ def xack(key, group, *ids)
274
+ args = [:xack, key, group].concat(ids.flatten)
275
+ send_command(args)
276
+ end
277
+
278
+ # Changes the ownership of a pending entry
279
+ #
280
+ # @example With splatted entry ids
281
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-1', '0-2')
282
+ # @example With arrayed entry ids
283
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2])
284
+ # @example With idle option
285
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], idle: 1000)
286
+ # @example With time option
287
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], time: 1542866959000)
288
+ # @example With retrycount option
289
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], retrycount: 10)
290
+ # @example With force option
291
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], force: true)
292
+ # @example With justid option
293
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], justid: true)
294
+ #
295
+ # @param key [String] the stream key
296
+ # @param group [String] the consumer group name
297
+ # @param consumer [String] the consumer name
298
+ # @param min_idle_time [Integer] the number of milliseconds
299
+ # @param ids [Array<String>] one or multiple entry ids
300
+ # @param opts [Hash] several options for `XCLAIM` command
301
+ #
302
+ # @option opts [Integer] :idle the number of milliseconds as last time it was delivered of the entry
303
+ # @option opts [Integer] :time the number of milliseconds as a specific Unix Epoch time
304
+ # @option opts [Integer] :retrycount the number of retry counter
305
+ # @option opts [Boolean] :force whether to create the pending entry to the pending entries list or not
306
+ # @option opts [Boolean] :justid whether to fetch just an array of entry ids or not
307
+ #
308
+ # @return [Hash{String => Hash}] the entries successfully claimed
309
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
310
+ def xclaim(key, group, consumer, min_idle_time, *ids, **opts)
311
+ args = [:xclaim, key, group, consumer, min_idle_time].concat(ids.flatten)
312
+ args.concat(['IDLE', opts[:idle].to_i]) if opts[:idle]
313
+ args.concat(['TIME', opts[:time].to_i]) if opts[:time]
314
+ args.concat(['RETRYCOUNT', opts[:retrycount]]) if opts[:retrycount]
315
+ args << 'FORCE' if opts[:force]
316
+ args << 'JUSTID' if opts[:justid]
317
+ blk = opts[:justid] ? Noop : HashifyStreamEntries
318
+ send_command(args, &blk)
319
+ end
320
+
321
+ # Transfers ownership of pending stream entries that match the specified criteria.
322
+ #
323
+ # @example Claim next pending message stuck > 5 minutes and mark as retry
324
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0')
325
+ # @example Claim 50 next pending messages stuck > 5 minutes and mark as retry
326
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', count: 50)
327
+ # @example Claim next pending message stuck > 5 minutes and don't mark as retry
328
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', justid: true)
329
+ # @example Claim next pending message after this id stuck > 5 minutes and mark as retry
330
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '1641321233-0')
331
+ #
332
+ # @param key [String] the stream key
333
+ # @param group [String] the consumer group name
334
+ # @param consumer [String] the consumer name
335
+ # @param min_idle_time [Integer] the number of milliseconds
336
+ # @param start [String] entry id to start scanning from or 0-0 for everything
337
+ # @param count [Integer] number of messages to claim (default 1)
338
+ # @param justid [Boolean] whether to fetch just an array of entry ids or not.
339
+ # Does not increment retry count when true
340
+ #
341
+ # @return [Hash{String => Hash}] the entries successfully claimed
342
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
343
+ def xautoclaim(key, group, consumer, min_idle_time, start, count: nil, justid: false)
344
+ args = [:xautoclaim, key, group, consumer, min_idle_time, start]
345
+ if count
346
+ args << 'COUNT' << count.to_s
347
+ end
348
+ args << 'JUSTID' if justid
349
+ blk = justid ? HashifyStreamAutoclaimJustId : HashifyStreamAutoclaim
350
+ send_command(args, &blk)
351
+ end
352
+
353
+ # Fetches not acknowledging pending entries
354
+ #
355
+ # @example With key and group
356
+ # redis.xpending('mystream', 'mygroup')
357
+ # @example With range options
358
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10)
359
+ # @example With range and idle time options
360
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, idle: 9000)
361
+ # @example With range and consumer options
362
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, 'consumer1')
363
+ #
364
+ # @param key [String] the stream key
365
+ # @param group [String] the consumer group name
366
+ # @param start [String] start first entry id of range
367
+ # @param end [String] end last entry id of range
368
+ # @param count [Integer] count the number of entries as limit
369
+ # @param consumer [String] the consumer name
370
+ #
371
+ # @option opts [Integer] :idle pending message minimum idle time in milliseconds
372
+ #
373
+ # @return [Hash] the summary of pending entries
374
+ # @return [Array<Hash>] the pending entries details if options were specified
375
+ def xpending(key, group, *args, idle: nil)
376
+ command_args = [:xpending, key, group]
377
+ command_args << 'IDLE' << Integer(idle) if idle
378
+ case args.size
379
+ when 0, 3, 4
380
+ command_args.concat(args)
381
+ else
382
+ raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 2, 5 or 6)"
383
+ end
384
+
385
+ summary_needed = args.empty?
386
+ blk = summary_needed ? HashifyStreamPendings : HashifyStreamPendingDetails
387
+ send_command(command_args, &blk)
388
+ end
389
+
390
+ private
391
+
392
+ def _xread(args, keys, ids, blocking_timeout_msec)
393
+ keys = keys.is_a?(Array) ? keys : [keys]
394
+ ids = ids.is_a?(Array) ? ids : [ids]
395
+ args << 'STREAMS'
396
+ args.concat(keys)
397
+ args.concat(ids)
398
+
399
+ if blocking_timeout_msec.nil?
400
+ send_command(args, &HashifyStreams)
401
+ elsif blocking_timeout_msec.to_f.zero?
402
+ send_blocking_command(args, 0, &HashifyStreams)
403
+ else
404
+ send_blocking_command(args, blocking_timeout_msec.to_f / 1_000, &HashifyStreams)
405
+ end
406
+ end
407
+ end
408
+ end
409
+ end