redis 4.4.0 → 4.8.1

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