redis 3.3.3 → 5.0.5

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 (136) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +280 -12
  3. data/README.md +141 -147
  4. data/lib/redis/client.rb +77 -539
  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 +285 -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 +818 -0
  18. data/lib/redis/commands/streams.rb +384 -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 +235 -0
  22. data/lib/redis/distributed.rb +300 -108
  23. data/lib/redis/errors.rb +22 -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 +113 -2685
  29. metadata +40 -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/db/.gitkeep +0 -0
  73. data/test/distributed_blocking_commands_test.rb +0 -46
  74. data/test/distributed_commands_on_hashes_test.rb +0 -10
  75. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  76. data/test/distributed_commands_on_lists_test.rb +0 -22
  77. data/test/distributed_commands_on_sets_test.rb +0 -83
  78. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  79. data/test/distributed_commands_on_strings_test.rb +0 -59
  80. data/test/distributed_commands_on_value_types_test.rb +0 -95
  81. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  82. data/test/distributed_connection_handling_test.rb +0 -23
  83. data/test/distributed_internals_test.rb +0 -79
  84. data/test/distributed_key_tags_test.rb +0 -52
  85. data/test/distributed_persistence_control_commands_test.rb +0 -26
  86. data/test/distributed_publish_subscribe_test.rb +0 -92
  87. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  88. data/test/distributed_scripting_test.rb +0 -102
  89. data/test/distributed_sorting_test.rb +0 -20
  90. data/test/distributed_test.rb +0 -58
  91. data/test/distributed_transactions_test.rb +0 -32
  92. data/test/encoding_test.rb +0 -18
  93. data/test/error_replies_test.rb +0 -59
  94. data/test/fork_safety_test.rb +0 -65
  95. data/test/helper.rb +0 -232
  96. data/test/helper_test.rb +0 -24
  97. data/test/internals_test.rb +0 -457
  98. data/test/lint/blocking_commands.rb +0 -150
  99. data/test/lint/hashes.rb +0 -162
  100. data/test/lint/hyper_log_log.rb +0 -60
  101. data/test/lint/lists.rb +0 -143
  102. data/test/lint/sets.rb +0 -140
  103. data/test/lint/sorted_sets.rb +0 -316
  104. data/test/lint/strings.rb +0 -260
  105. data/test/lint/value_types.rb +0 -122
  106. data/test/persistence_control_commands_test.rb +0 -26
  107. data/test/pipelining_commands_test.rb +0 -242
  108. data/test/publish_subscribe_test.rb +0 -282
  109. data/test/remote_server_control_commands_test.rb +0 -118
  110. data/test/scanning_test.rb +0 -413
  111. data/test/scripting_test.rb +0 -78
  112. data/test/sentinel_command_test.rb +0 -80
  113. data/test/sentinel_test.rb +0 -255
  114. data/test/sorting_test.rb +0 -59
  115. data/test/ssl_test.rb +0 -73
  116. data/test/support/connection/hiredis.rb +0 -1
  117. data/test/support/connection/ruby.rb +0 -1
  118. data/test/support/connection/synchrony.rb +0 -17
  119. data/test/support/redis_mock.rb +0 -130
  120. data/test/support/ssl/gen_certs.sh +0 -31
  121. data/test/support/ssl/trusted-ca.crt +0 -25
  122. data/test/support/ssl/trusted-ca.key +0 -27
  123. data/test/support/ssl/trusted-cert.crt +0 -81
  124. data/test/support/ssl/trusted-cert.key +0 -28
  125. data/test/support/ssl/untrusted-ca.crt +0 -26
  126. data/test/support/ssl/untrusted-ca.key +0 -27
  127. data/test/support/ssl/untrusted-cert.crt +0 -82
  128. data/test/support/ssl/untrusted-cert.key +0 -28
  129. data/test/support/wire/synchrony.rb +0 -24
  130. data/test/support/wire/thread.rb +0 -5
  131. data/test/synchrony_driver.rb +0 -88
  132. data/test/test.conf.erb +0 -9
  133. data/test/thread_safety_test.rb +0 -62
  134. data/test/transactions_test.rb +0 -264
  135. data/test/unknown_commands_test.rb +0 -14
  136. data/test/url_param_test.rb +0 -138
@@ -0,0 +1,384 @@
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)
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
+ #
47
+ # @return [String] the entry id
48
+ def xadd(key, entry, approximate: nil, maxlen: nil, id: '*')
49
+ args = [:xadd, key]
50
+ if maxlen
51
+ args << "MAXLEN"
52
+ args << "~" if approximate
53
+ args << maxlen
54
+ end
55
+ args << id
56
+ args.concat(entry.flatten)
57
+ send_command(args)
58
+ end
59
+
60
+ # Trims older entries of the stream if needed.
61
+ #
62
+ # @example Without options
63
+ # redis.xtrim('mystream', 1000)
64
+ # @example With options
65
+ # redis.xtrim('mystream', 1000, approximate: true)
66
+ #
67
+ # @param key [String] the stream key
68
+ # @param mexlen [Integer] max length of entries
69
+ # @param approximate [Boolean] whether to add `~` modifier of maxlen or not
70
+ #
71
+ # @return [Integer] the number of entries actually deleted
72
+ def xtrim(key, maxlen, approximate: false)
73
+ args = [:xtrim, key, 'MAXLEN', (approximate ? '~' : nil), maxlen].compact
74
+ send_command(args)
75
+ end
76
+
77
+ # Delete entries by entry ids.
78
+ #
79
+ # @example With splatted entry ids
80
+ # redis.xdel('mystream', '0-1', '0-2')
81
+ # @example With arrayed entry ids
82
+ # redis.xdel('mystream', ['0-1', '0-2'])
83
+ #
84
+ # @param key [String] the stream key
85
+ # @param ids [Array<String>] one or multiple entry ids
86
+ #
87
+ # @return [Integer] the number of entries actually deleted
88
+ def xdel(key, *ids)
89
+ args = [:xdel, key].concat(ids.flatten)
90
+ send_command(args)
91
+ end
92
+
93
+ # Fetches entries of the stream in ascending order.
94
+ #
95
+ # @example Without options
96
+ # redis.xrange('mystream')
97
+ # @example With a specific start
98
+ # redis.xrange('mystream', '0-1')
99
+ # @example With a specific start and end
100
+ # redis.xrange('mystream', '0-1', '0-3')
101
+ # @example With count options
102
+ # redis.xrange('mystream', count: 10)
103
+ #
104
+ # @param key [String] the stream key
105
+ # @param start [String] first entry id of range, default value is `-`
106
+ # @param end [String] last entry id of range, default value is `+`
107
+ # @param count [Integer] the number of entries as limit
108
+ #
109
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
110
+ def xrange(key, start = '-', range_end = '+', count: nil)
111
+ args = [:xrange, key, start, range_end]
112
+ args.concat(['COUNT', count]) if count
113
+ send_command(args, &HashifyStreamEntries)
114
+ end
115
+
116
+ # Fetches entries of the stream in descending order.
117
+ #
118
+ # @example Without options
119
+ # redis.xrevrange('mystream')
120
+ # @example With a specific end
121
+ # redis.xrevrange('mystream', '0-3')
122
+ # @example With a specific end and start
123
+ # redis.xrevrange('mystream', '0-3', '0-1')
124
+ # @example With count options
125
+ # redis.xrevrange('mystream', count: 10)
126
+ #
127
+ # @param key [String] the stream key
128
+ # @param end [String] first entry id of range, default value is `+`
129
+ # @param start [String] last entry id of range, default value is `-`
130
+ # @params count [Integer] the number of entries as limit
131
+ #
132
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
133
+ def xrevrange(key, range_end = '+', start = '-', count: nil)
134
+ args = [:xrevrange, key, range_end, start]
135
+ args.concat(['COUNT', count]) if count
136
+ send_command(args, &HashifyStreamEntries)
137
+ end
138
+
139
+ # Returns the number of entries inside a stream.
140
+ #
141
+ # @example With key
142
+ # redis.xlen('mystream')
143
+ #
144
+ # @param key [String] the stream key
145
+ #
146
+ # @return [Integer] the number of entries
147
+ def xlen(key)
148
+ send_command([:xlen, key])
149
+ end
150
+
151
+ # Fetches entries from one or multiple streams. Optionally blocking.
152
+ #
153
+ # @example With a key
154
+ # redis.xread('mystream', '0-0')
155
+ # @example With multiple keys
156
+ # redis.xread(%w[mystream1 mystream2], %w[0-0 0-0])
157
+ # @example With count option
158
+ # redis.xread('mystream', '0-0', count: 2)
159
+ # @example With block option
160
+ # redis.xread('mystream', '$', block: 1000)
161
+ #
162
+ # @param keys [Array<String>] one or multiple stream keys
163
+ # @param ids [Array<String>] one or multiple entry ids
164
+ # @param count [Integer] the number of entries as limit per stream
165
+ # @param block [Integer] the number of milliseconds as blocking timeout
166
+ #
167
+ # @return [Hash{String => Hash{String => Hash}}] the entries
168
+ def xread(keys, ids, count: nil, block: nil)
169
+ args = [:xread]
170
+ args << 'COUNT' << count if count
171
+ args << 'BLOCK' << block.to_i if block
172
+ _xread(args, keys, ids, block)
173
+ end
174
+
175
+ # Manages the consumer group of the stream.
176
+ #
177
+ # @example With `create` subcommand
178
+ # redis.xgroup(:create, 'mystream', 'mygroup', '$')
179
+ # @example With `setid` subcommand
180
+ # redis.xgroup(:setid, 'mystream', 'mygroup', '$')
181
+ # @example With `destroy` subcommand
182
+ # redis.xgroup(:destroy, 'mystream', 'mygroup')
183
+ # @example With `delconsumer` subcommand
184
+ # redis.xgroup(:delconsumer, 'mystream', 'mygroup', 'consumer1')
185
+ #
186
+ # @param subcommand [String] `create` `setid` `destroy` `delconsumer`
187
+ # @param key [String] the stream key
188
+ # @param group [String] the consumer group name
189
+ # @param id_or_consumer [String]
190
+ # * the entry id or `$`, required if subcommand is `create` or `setid`
191
+ # * the consumer name, required if subcommand is `delconsumer`
192
+ # @param mkstream [Boolean] whether to create an empty stream automatically or not
193
+ #
194
+ # @return [String] `OK` if subcommand is `create` or `setid`
195
+ # @return [Integer] effected count if subcommand is `destroy` or `delconsumer`
196
+ def xgroup(subcommand, key, group, id_or_consumer = nil, mkstream: false)
197
+ args = [:xgroup, subcommand, key, group, id_or_consumer, (mkstream ? 'MKSTREAM' : nil)].compact
198
+ send_command(args)
199
+ end
200
+
201
+ # Fetches a subset of the entries from one or multiple streams related with the consumer group.
202
+ # Optionally blocking.
203
+ #
204
+ # @example With a key
205
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>')
206
+ # @example With multiple keys
207
+ # redis.xreadgroup('mygroup', 'consumer1', %w[mystream1 mystream2], %w[> >])
208
+ # @example With count option
209
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', count: 2)
210
+ # @example With block option
211
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', block: 1000)
212
+ # @example With noack option
213
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', noack: true)
214
+ #
215
+ # @param group [String] the consumer group name
216
+ # @param consumer [String] the consumer name
217
+ # @param keys [Array<String>] one or multiple stream keys
218
+ # @param ids [Array<String>] one or multiple entry ids
219
+ # @param opts [Hash] several options for `XREADGROUP` command
220
+ #
221
+ # @option opts [Integer] :count the number of entries as limit
222
+ # @option opts [Integer] :block the number of milliseconds as blocking timeout
223
+ # @option opts [Boolean] :noack whether message loss is acceptable or not
224
+ #
225
+ # @return [Hash{String => Hash{String => Hash}}] the entries
226
+ def xreadgroup(group, consumer, keys, ids, count: nil, block: nil, noack: nil)
227
+ args = [:xreadgroup, 'GROUP', group, consumer]
228
+ args << 'COUNT' << count if count
229
+ args << 'BLOCK' << block.to_i if block
230
+ args << 'NOACK' if noack
231
+ _xread(args, keys, ids, block)
232
+ end
233
+
234
+ # Removes one or multiple entries from the pending entries list of a stream consumer group.
235
+ #
236
+ # @example With a entry id
237
+ # redis.xack('mystream', 'mygroup', '1526569495631-0')
238
+ # @example With splatted entry ids
239
+ # redis.xack('mystream', 'mygroup', '0-1', '0-2')
240
+ # @example With arrayed entry ids
241
+ # redis.xack('mystream', 'mygroup', %w[0-1 0-2])
242
+ #
243
+ # @param key [String] the stream key
244
+ # @param group [String] the consumer group name
245
+ # @param ids [Array<String>] one or multiple entry ids
246
+ #
247
+ # @return [Integer] the number of entries successfully acknowledged
248
+ def xack(key, group, *ids)
249
+ args = [:xack, key, group].concat(ids.flatten)
250
+ send_command(args)
251
+ end
252
+
253
+ # Changes the ownership of a pending entry
254
+ #
255
+ # @example With splatted entry ids
256
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-1', '0-2')
257
+ # @example With arrayed entry ids
258
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2])
259
+ # @example With idle option
260
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], idle: 1000)
261
+ # @example With time option
262
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], time: 1542866959000)
263
+ # @example With retrycount option
264
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], retrycount: 10)
265
+ # @example With force option
266
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], force: true)
267
+ # @example With justid option
268
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], justid: true)
269
+ #
270
+ # @param key [String] the stream key
271
+ # @param group [String] the consumer group name
272
+ # @param consumer [String] the consumer name
273
+ # @param min_idle_time [Integer] the number of milliseconds
274
+ # @param ids [Array<String>] one or multiple entry ids
275
+ # @param opts [Hash] several options for `XCLAIM` command
276
+ #
277
+ # @option opts [Integer] :idle the number of milliseconds as last time it was delivered of the entry
278
+ # @option opts [Integer] :time the number of milliseconds as a specific Unix Epoch time
279
+ # @option opts [Integer] :retrycount the number of retry counter
280
+ # @option opts [Boolean] :force whether to create the pending entry to the pending entries list or not
281
+ # @option opts [Boolean] :justid whether to fetch just an array of entry ids or not
282
+ #
283
+ # @return [Hash{String => Hash}] the entries successfully claimed
284
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
285
+ def xclaim(key, group, consumer, min_idle_time, *ids, **opts)
286
+ args = [:xclaim, key, group, consumer, min_idle_time].concat(ids.flatten)
287
+ args.concat(['IDLE', opts[:idle].to_i]) if opts[:idle]
288
+ args.concat(['TIME', opts[:time].to_i]) if opts[:time]
289
+ args.concat(['RETRYCOUNT', opts[:retrycount]]) if opts[:retrycount]
290
+ args << 'FORCE' if opts[:force]
291
+ args << 'JUSTID' if opts[:justid]
292
+ blk = opts[:justid] ? Noop : HashifyStreamEntries
293
+ send_command(args, &blk)
294
+ end
295
+
296
+ # Transfers ownership of pending stream entries that match the specified criteria.
297
+ #
298
+ # @example Claim next pending message stuck > 5 minutes and mark as retry
299
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0')
300
+ # @example Claim 50 next pending messages stuck > 5 minutes and mark as retry
301
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', count: 50)
302
+ # @example Claim next pending message stuck > 5 minutes and don't mark as retry
303
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', justid: true)
304
+ # @example Claim next pending message after this id stuck > 5 minutes and mark as retry
305
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '1641321233-0')
306
+ #
307
+ # @param key [String] the stream key
308
+ # @param group [String] the consumer group name
309
+ # @param consumer [String] the consumer name
310
+ # @param min_idle_time [Integer] the number of milliseconds
311
+ # @param start [String] entry id to start scanning from or 0-0 for everything
312
+ # @param count [Integer] number of messages to claim (default 1)
313
+ # @param justid [Boolean] whether to fetch just an array of entry ids or not.
314
+ # Does not increment retry count when true
315
+ #
316
+ # @return [Hash{String => Hash}] the entries successfully claimed
317
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
318
+ def xautoclaim(key, group, consumer, min_idle_time, start, count: nil, justid: false)
319
+ args = [:xautoclaim, key, group, consumer, min_idle_time, start]
320
+ if count
321
+ args << 'COUNT' << count.to_s
322
+ end
323
+ args << 'JUSTID' if justid
324
+ blk = justid ? HashifyStreamAutoclaimJustId : HashifyStreamAutoclaim
325
+ send_command(args, &blk)
326
+ end
327
+
328
+ # Fetches not acknowledging pending entries
329
+ #
330
+ # @example With key and group
331
+ # redis.xpending('mystream', 'mygroup')
332
+ # @example With range options
333
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10)
334
+ # @example With range and idle time options
335
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, idle: 9000)
336
+ # @example With range and consumer options
337
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, 'consumer1')
338
+ #
339
+ # @param key [String] the stream key
340
+ # @param group [String] the consumer group name
341
+ # @param start [String] start first entry id of range
342
+ # @param end [String] end last entry id of range
343
+ # @param count [Integer] count the number of entries as limit
344
+ # @param consumer [String] the consumer name
345
+ #
346
+ # @option opts [Integer] :idle pending message minimum idle time in milliseconds
347
+ #
348
+ # @return [Hash] the summary of pending entries
349
+ # @return [Array<Hash>] the pending entries details if options were specified
350
+ def xpending(key, group, *args, idle: nil)
351
+ command_args = [:xpending, key, group]
352
+ command_args << 'IDLE' << Integer(idle) if idle
353
+ case args.size
354
+ when 0, 3, 4
355
+ command_args.concat(args)
356
+ else
357
+ raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 2, 5 or 6)"
358
+ end
359
+
360
+ summary_needed = args.empty?
361
+ blk = summary_needed ? HashifyStreamPendings : HashifyStreamPendingDetails
362
+ send_command(command_args, &blk)
363
+ end
364
+
365
+ private
366
+
367
+ def _xread(args, keys, ids, blocking_timeout_msec)
368
+ keys = keys.is_a?(Array) ? keys : [keys]
369
+ ids = ids.is_a?(Array) ? ids : [ids]
370
+ args << 'STREAMS'
371
+ args.concat(keys)
372
+ args.concat(ids)
373
+
374
+ if blocking_timeout_msec.nil?
375
+ send_command(args, &HashifyStreams)
376
+ elsif blocking_timeout_msec.to_f.zero?
377
+ send_blocking_command(args, 0, &HashifyStreams)
378
+ else
379
+ send_blocking_command(args, blocking_timeout_msec.to_f / 1_000, &HashifyStreams)
380
+ end
381
+ end
382
+ end
383
+ end
384
+ end
@@ -0,0 +1,314 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Strings
6
+ # Decrement the integer value of a key by one.
7
+ #
8
+ # @example
9
+ # redis.decr("value")
10
+ # # => 4
11
+ #
12
+ # @param [String] key
13
+ # @return [Integer] value after decrementing it
14
+ def decr(key)
15
+ send_command([:decr, key])
16
+ end
17
+
18
+ # Decrement the integer value of a key by the given number.
19
+ #
20
+ # @example
21
+ # redis.decrby("value", 5)
22
+ # # => 0
23
+ #
24
+ # @param [String] key
25
+ # @param [Integer] decrement
26
+ # @return [Integer] value after decrementing it
27
+ def decrby(key, decrement)
28
+ send_command([:decrby, key, Integer(decrement)])
29
+ end
30
+
31
+ # Increment the integer value of a key by one.
32
+ #
33
+ # @example
34
+ # redis.incr("value")
35
+ # # => 6
36
+ #
37
+ # @param [String] key
38
+ # @return [Integer] value after incrementing it
39
+ def incr(key)
40
+ send_command([:incr, key])
41
+ end
42
+
43
+ # Increment the integer value of a key by the given integer number.
44
+ #
45
+ # @example
46
+ # redis.incrby("value", 5)
47
+ # # => 10
48
+ #
49
+ # @param [String] key
50
+ # @param [Integer] increment
51
+ # @return [Integer] value after incrementing it
52
+ def incrby(key, increment)
53
+ send_command([:incrby, key, Integer(increment)])
54
+ end
55
+
56
+ # Increment the numeric value of a key by the given float number.
57
+ #
58
+ # @example
59
+ # redis.incrbyfloat("value", 1.23)
60
+ # # => 1.23
61
+ #
62
+ # @param [String] key
63
+ # @param [Float] increment
64
+ # @return [Float] value after incrementing it
65
+ def incrbyfloat(key, increment)
66
+ send_command([:incrbyfloat, key, Float(increment)], &Floatify)
67
+ end
68
+
69
+ # Set the string value of a key.
70
+ #
71
+ # @param [String] key
72
+ # @param [String] value
73
+ # @param [Hash] options
74
+ # - `:ex => Integer`: Set the specified expire time, in seconds.
75
+ # - `:px => Integer`: Set the specified expire time, in milliseconds.
76
+ # - `:exat => Integer` : Set the specified Unix time at which the key will expire, in seconds.
77
+ # - `:pxat => Integer` : Set the specified Unix time at which the key will expire, in milliseconds.
78
+ # - `:nx => true`: Only set the key if it does not already exist.
79
+ # - `:xx => true`: Only set the key if it already exist.
80
+ # - `:keepttl => true`: Retain the time to live associated with the key.
81
+ # - `:get => true`: Return the old string stored at key, or nil if key did not exist.
82
+ # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
83
+ def set(key, value, ex: nil, px: nil, exat: nil, pxat: nil, nx: nil, xx: nil, keepttl: nil, get: nil)
84
+ args = [:set, key, value.to_s]
85
+ args << "EX" << Integer(ex) if ex
86
+ args << "PX" << Integer(px) if px
87
+ args << "EXAT" << Integer(exat) if exat
88
+ args << "PXAT" << Integer(pxat) if pxat
89
+ args << "NX" if nx
90
+ args << "XX" if xx
91
+ args << "KEEPTTL" if keepttl
92
+ args << "GET" if get
93
+
94
+ if nx || xx
95
+ send_command(args, &BoolifySet)
96
+ else
97
+ send_command(args)
98
+ end
99
+ end
100
+
101
+ # Set the time to live in seconds of a key.
102
+ #
103
+ # @param [String] key
104
+ # @param [Integer] ttl
105
+ # @param [String] value
106
+ # @return [String] `"OK"`
107
+ def setex(key, ttl, value)
108
+ send_command([:setex, key, Integer(ttl), value.to_s])
109
+ end
110
+
111
+ # Set the time to live in milliseconds of a key.
112
+ #
113
+ # @param [String] key
114
+ # @param [Integer] ttl
115
+ # @param [String] value
116
+ # @return [String] `"OK"`
117
+ def psetex(key, ttl, value)
118
+ send_command([:psetex, key, Integer(ttl), value.to_s])
119
+ end
120
+
121
+ # Set the value of a key, only if the key does not exist.
122
+ #
123
+ # @param [String] key
124
+ # @param [String] value
125
+ # @return [Boolean] whether the key was set or not
126
+ def setnx(key, value)
127
+ send_command([:setnx, key, value.to_s], &Boolify)
128
+ end
129
+
130
+ # Set one or more values.
131
+ #
132
+ # @example
133
+ # redis.mset("key1", "v1", "key2", "v2")
134
+ # # => "OK"
135
+ #
136
+ # @param [Array<String>] args array of keys and values
137
+ # @return [String] `"OK"`
138
+ #
139
+ # @see #mapped_mset
140
+ def mset(*args)
141
+ send_command([:mset] + args)
142
+ end
143
+
144
+ # Set one or more values.
145
+ #
146
+ # @example
147
+ # redis.mapped_mset({ "f1" => "v1", "f2" => "v2" })
148
+ # # => "OK"
149
+ #
150
+ # @param [Hash] hash keys mapping to values
151
+ # @return [String] `"OK"`
152
+ #
153
+ # @see #mset
154
+ def mapped_mset(hash)
155
+ mset(hash.flatten)
156
+ end
157
+
158
+ # Set one or more values, only if none of the keys exist.
159
+ #
160
+ # @example
161
+ # redis.msetnx("key1", "v1", "key2", "v2")
162
+ # # => true
163
+ #
164
+ # @param [Array<String>] args array of keys and values
165
+ # @return [Boolean] whether or not all values were set
166
+ #
167
+ # @see #mapped_msetnx
168
+ def msetnx(*args)
169
+ send_command([:msetnx, *args], &Boolify)
170
+ end
171
+
172
+ # Set one or more values, only if none of the keys exist.
173
+ #
174
+ # @example
175
+ # redis.mapped_msetnx({ "key1" => "v1", "key2" => "v2" })
176
+ # # => true
177
+ #
178
+ # @param [Hash] hash keys mapping to values
179
+ # @return [Boolean] whether or not all values were set
180
+ #
181
+ # @see #msetnx
182
+ def mapped_msetnx(hash)
183
+ msetnx(hash.flatten)
184
+ end
185
+
186
+ # Get the value of a key.
187
+ #
188
+ # @param [String] key
189
+ # @return [String]
190
+ def get(key)
191
+ send_command([:get, key])
192
+ end
193
+
194
+ # Get the values of all the given keys.
195
+ #
196
+ # @example
197
+ # redis.mget("key1", "key2")
198
+ # # => ["v1", "v2"]
199
+ #
200
+ # @param [Array<String>] keys
201
+ # @return [Array<String>] an array of values for the specified keys
202
+ #
203
+ # @see #mapped_mget
204
+ def mget(*keys, &blk)
205
+ keys.flatten!(1)
206
+ send_command([:mget, *keys], &blk)
207
+ end
208
+
209
+ # Get the values of all the given keys.
210
+ #
211
+ # @example
212
+ # redis.mapped_mget("key1", "key2")
213
+ # # => { "key1" => "v1", "key2" => "v2" }
214
+ #
215
+ # @param [Array<String>] keys array of keys
216
+ # @return [Hash] a hash mapping the specified keys to their values
217
+ #
218
+ # @see #mget
219
+ def mapped_mget(*keys)
220
+ mget(*keys) do |reply|
221
+ if reply.is_a?(Array)
222
+ Hash[keys.zip(reply)]
223
+ else
224
+ reply
225
+ end
226
+ end
227
+ end
228
+
229
+ # Overwrite part of a string at key starting at the specified offset.
230
+ #
231
+ # @param [String] key
232
+ # @param [Integer] offset byte offset
233
+ # @param [String] value
234
+ # @return [Integer] length of the string after it was modified
235
+ def setrange(key, offset, value)
236
+ send_command([:setrange, key, Integer(offset), value.to_s])
237
+ end
238
+
239
+ # Get a substring of the string stored at a key.
240
+ #
241
+ # @param [String] key
242
+ # @param [Integer] start zero-based start offset
243
+ # @param [Integer] stop zero-based end offset. Use -1 for representing
244
+ # the end of the string
245
+ # @return [Integer] `0` or `1`
246
+ def getrange(key, start, stop)
247
+ send_command([:getrange, key, Integer(start), Integer(stop)])
248
+ end
249
+
250
+ # Append a value to a key.
251
+ #
252
+ # @param [String] key
253
+ # @param [String] value value to append
254
+ # @return [Integer] length of the string after appending
255
+ def append(key, value)
256
+ send_command([:append, key, value])
257
+ end
258
+
259
+ # Set the string value of a key and return its old value.
260
+ #
261
+ # @param [String] key
262
+ # @param [String] value value to replace the current value with
263
+ # @return [String] the old value stored in the key, or `nil` if the key
264
+ # did not exist
265
+ def getset(key, value)
266
+ send_command([:getset, key, value.to_s])
267
+ end
268
+
269
+ # Get the value of key and delete the key. This command is similar to GET,
270
+ # except for the fact that it also deletes the key on success.
271
+ #
272
+ # @param [String] key
273
+ # @return [String] the old value stored in the key, or `nil` if the key
274
+ # did not exist
275
+ def getdel(key)
276
+ send_command([:getdel, key])
277
+ end
278
+
279
+ # Get the value of key and optionally set its expiration. GETEX is similar to
280
+ # GET, but is a write command with additional options. When no options are
281
+ # provided, GETEX behaves like GET.
282
+ #
283
+ # @param [String] key
284
+ # @param [Hash] options
285
+ # - `:ex => Integer`: Set the specified expire time, in seconds.
286
+ # - `:px => Integer`: Set the specified expire time, in milliseconds.
287
+ # - `:exat => true`: Set the specified Unix time at which the key will
288
+ # expire, in seconds.
289
+ # - `:pxat => true`: Set the specified Unix time at which the key will
290
+ # expire, in milliseconds.
291
+ # - `:persist => true`: Remove the time to live associated with the key.
292
+ # @return [String] The value of key, or nil when key does not exist.
293
+ def getex(key, ex: nil, px: nil, exat: nil, pxat: nil, persist: false)
294
+ args = [:getex, key]
295
+ args << "EX" << Integer(ex) if ex
296
+ args << "PX" << Integer(px) if px
297
+ args << "EXAT" << Integer(exat) if exat
298
+ args << "PXAT" << Integer(pxat) if pxat
299
+ args << "PERSIST" if persist
300
+
301
+ send_command(args)
302
+ end
303
+
304
+ # Get the length of the value stored in a key.
305
+ #
306
+ # @param [String] key
307
+ # @return [Integer] the length of the value stored in the key, or 0
308
+ # if the key does not exist
309
+ def strlen(key)
310
+ send_command([:strlen, key])
311
+ end
312
+ end
313
+ end
314
+ end