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,455 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Keys
6
+ # Scan the keyspace
7
+ #
8
+ # @example Retrieve the first batch of keys
9
+ # redis.scan(0)
10
+ # # => ["4", ["key:21", "key:47", "key:42"]]
11
+ # @example Retrieve a batch of keys matching a pattern
12
+ # redis.scan(4, :match => "key:1?")
13
+ # # => ["92", ["key:13", "key:18"]]
14
+ # @example Retrieve a batch of keys of a certain type
15
+ # redis.scan(92, :type => "zset")
16
+ # # => ["173", ["sortedset:14", "sortedset:78"]]
17
+ #
18
+ # @param [String, Integer] cursor the cursor of the iteration
19
+ # @param [Hash] options
20
+ # - `:match => String`: only return keys matching the pattern
21
+ # - `:count => Integer`: return count keys at most per iteration
22
+ # - `:type => String`: return keys only of the given type
23
+ #
24
+ # @return [String, Array<String>] the next cursor and all found keys
25
+ def scan(cursor, **options)
26
+ _scan(:scan, cursor, [], **options)
27
+ end
28
+
29
+ # Scan the keyspace
30
+ #
31
+ # @example Retrieve all of the keys (with possible duplicates)
32
+ # redis.scan_each.to_a
33
+ # # => ["key:21", "key:47", "key:42"]
34
+ # @example Execute block for each key matching a pattern
35
+ # redis.scan_each(:match => "key:1?") {|key| puts key}
36
+ # # => key:13
37
+ # # => key:18
38
+ # @example Execute block for each key of a type
39
+ # redis.scan_each(:type => "hash") {|key| puts redis.type(key)}
40
+ # # => "hash"
41
+ # # => "hash"
42
+ #
43
+ # @param [Hash] options
44
+ # - `:match => String`: only return keys matching the pattern
45
+ # - `:count => Integer`: return count keys at most per iteration
46
+ # - `:type => String`: return keys only of the given type
47
+ #
48
+ # @return [Enumerator] an enumerator for all found keys
49
+ def scan_each(**options, &block)
50
+ return to_enum(:scan_each, **options) unless block_given?
51
+
52
+ cursor = 0
53
+ loop do
54
+ cursor, keys = scan(cursor, **options)
55
+ keys.each(&block)
56
+ break if cursor == "0"
57
+ end
58
+ end
59
+
60
+ # Remove the expiration from a key.
61
+ #
62
+ # @param [String] key
63
+ # @return [Boolean] whether the timeout was removed or not
64
+ def persist(key)
65
+ send_command([:persist, key], &Boolify)
66
+ end
67
+
68
+ # Set a key's time to live in seconds.
69
+ #
70
+ # @param [String] key
71
+ # @param [Integer] seconds time to live
72
+ # @param [Hash] options
73
+ # - `:nx => true`: Set expiry only when the key has no expiry.
74
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
75
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
76
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
77
+ # @return [Boolean] whether the timeout was set or not
78
+ def expire(key, seconds, nx: nil, xx: nil, gt: nil, lt: nil)
79
+ args = [:expire, key, seconds]
80
+ args << "NX" if nx
81
+ args << "XX" if xx
82
+ args << "GT" if gt
83
+ args << "LT" if lt
84
+
85
+ send_command(args, &Boolify)
86
+ end
87
+
88
+ # Set the expiration for a key as a UNIX timestamp.
89
+ #
90
+ # @param [String] key
91
+ # @param [Integer] unix_time expiry time specified as a UNIX timestamp
92
+ # @param [Hash] options
93
+ # - `:nx => true`: Set expiry only when the key has no expiry.
94
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
95
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
96
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
97
+ # @return [Boolean] whether the timeout was set or not
98
+ def expireat(key, unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
99
+ args = [:expireat, key, unix_time]
100
+ args << "NX" if nx
101
+ args << "XX" if xx
102
+ args << "GT" if gt
103
+ args << "LT" if lt
104
+
105
+ send_command(args, &Boolify)
106
+ end
107
+
108
+ # Get the time to live (in seconds) for a key.
109
+ #
110
+ # @param [String] key
111
+ # @return [Integer] remaining time to live in seconds.
112
+ #
113
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
114
+ # the key exist but has no associated expire.
115
+ #
116
+ # Starting with Redis 2.8 the return value in case of error changed:
117
+ #
118
+ # - The command returns -2 if the key does not exist.
119
+ # - The command returns -1 if the key exists but has no associated expire.
120
+ def ttl(key)
121
+ send_command([:ttl, key])
122
+ end
123
+
124
+ # Set a key's time to live in milliseconds.
125
+ #
126
+ # @param [String] key
127
+ # @param [Integer] milliseconds time to live
128
+ # @param [Hash] options
129
+ # - `:nx => true`: Set expiry only when the key has no expiry.
130
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
131
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
132
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
133
+ # @return [Boolean] whether the timeout was set or not
134
+ def pexpire(key, milliseconds, nx: nil, xx: nil, gt: nil, lt: nil)
135
+ args = [:pexpire, key, milliseconds]
136
+ args << "NX" if nx
137
+ args << "XX" if xx
138
+ args << "GT" if gt
139
+ args << "LT" if lt
140
+
141
+ send_command(args, &Boolify)
142
+ end
143
+
144
+ # Set the expiration for a key as number of milliseconds from UNIX Epoch.
145
+ #
146
+ # @param [String] key
147
+ # @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
148
+ # @param [Hash] options
149
+ # - `:nx => true`: Set expiry only when the key has no expiry.
150
+ # - `:xx => true`: Set expiry only when the key has an existing expiry.
151
+ # - `:gt => true`: Set expiry only when the new expiry is greater than current one.
152
+ # - `:lt => true`: Set expiry only when the new expiry is less than current one.
153
+ # @return [Boolean] whether the timeout was set or not
154
+ def pexpireat(key, ms_unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
155
+ args = [:pexpireat, key, ms_unix_time]
156
+ args << "NX" if nx
157
+ args << "XX" if xx
158
+ args << "GT" if gt
159
+ args << "LT" if lt
160
+
161
+ send_command(args, &Boolify)
162
+ end
163
+
164
+ # Get the time to live (in milliseconds) for a key.
165
+ #
166
+ # @param [String] key
167
+ # @return [Integer] remaining time to live in milliseconds
168
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
169
+ # the key exist but has no associated expire.
170
+ #
171
+ # Starting with Redis 2.8 the return value in case of error changed:
172
+ #
173
+ # - The command returns -2 if the key does not exist.
174
+ # - The command returns -1 if the key exists but has no associated expire.
175
+ def pttl(key)
176
+ send_command([:pttl, key])
177
+ end
178
+
179
+ # Return a serialized version of the value stored at a key.
180
+ #
181
+ # @param [String] key
182
+ # @return [String] serialized_value
183
+ def dump(key)
184
+ send_command([:dump, key])
185
+ end
186
+
187
+ # Create a key using the serialized value, previously obtained using DUMP.
188
+ #
189
+ # @param [String] key
190
+ # @param [String] ttl
191
+ # @param [String] serialized_value
192
+ # @param [Hash] options
193
+ # - `:replace => Boolean`: if false, raises an error if key already exists
194
+ # @raise [Redis::CommandError]
195
+ # @return [String] `"OK"`
196
+ def restore(key, ttl, serialized_value, replace: nil)
197
+ args = [:restore, key, ttl, serialized_value]
198
+ args << 'REPLACE' if replace
199
+
200
+ send_command(args)
201
+ end
202
+
203
+ # Transfer a key from the connected instance to another instance.
204
+ #
205
+ # @param [String, Array<String>] key
206
+ # @param [Hash] options
207
+ # - `:host => String`: host of instance to migrate to
208
+ # - `:port => Integer`: port of instance to migrate to
209
+ # - `:db => Integer`: database to migrate to (default: same as source)
210
+ # - `:timeout => Integer`: timeout (default: same as connection timeout)
211
+ # - `:copy => Boolean`: Do not remove the key from the local instance.
212
+ # - `:replace => Boolean`: Replace existing key on the remote instance.
213
+ # @return [String] `"OK"`
214
+ def migrate(key, options)
215
+ args = [:migrate]
216
+ args << (options[:host] || raise(':host not specified'))
217
+ args << (options[:port] || raise(':port not specified'))
218
+ args << (key.is_a?(String) ? key : '')
219
+ args << (options[:db] || @client.db).to_i
220
+ args << (options[:timeout] || @client.timeout).to_i
221
+ args << 'COPY' if options[:copy]
222
+ args << 'REPLACE' if options[:replace]
223
+ args += ['KEYS', *key] if key.is_a?(Array)
224
+
225
+ send_command(args)
226
+ end
227
+
228
+ # Delete one or more keys.
229
+ #
230
+ # @param [String, Array<String>] keys
231
+ # @return [Integer] number of keys that were deleted
232
+ def del(*keys)
233
+ keys.flatten!(1)
234
+ return 0 if keys.empty?
235
+
236
+ send_command([:del] + keys)
237
+ end
238
+
239
+ # Unlink one or more keys.
240
+ #
241
+ # @param [String, Array<String>] keys
242
+ # @return [Integer] number of keys that were unlinked
243
+ def unlink(*keys)
244
+ send_command([:unlink] + keys)
245
+ end
246
+
247
+ # Determine how many of the keys exists.
248
+ #
249
+ # @param [String, Array<String>] keys
250
+ # @return [Integer]
251
+ def exists(*keys)
252
+ if !Redis.exists_returns_integer && keys.size == 1
253
+ if Redis.exists_returns_integer.nil?
254
+ message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
255
+ "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
256
+ "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
257
+ "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0.0. " \
258
+ "(#{::Kernel.caller(1, 1).first})\n"
259
+
260
+ ::Redis.deprecate!(message)
261
+ end
262
+
263
+ exists?(*keys)
264
+ else
265
+ _exists(*keys)
266
+ end
267
+ end
268
+
269
+ def _exists(*keys)
270
+ send_command([:exists, *keys])
271
+ end
272
+
273
+ # Determine if any of the keys exists.
274
+ #
275
+ # @param [String, Array<String>] keys
276
+ # @return [Boolean]
277
+ def exists?(*keys)
278
+ send_command([:exists, *keys]) do |value|
279
+ value > 0
280
+ end
281
+ end
282
+
283
+ # Find all keys matching the given pattern.
284
+ #
285
+ # @param [String] pattern
286
+ # @return [Array<String>]
287
+ def keys(pattern = "*")
288
+ send_command([:keys, pattern]) do |reply|
289
+ if reply.is_a?(String)
290
+ reply.split(" ")
291
+ else
292
+ reply
293
+ end
294
+ end
295
+ end
296
+
297
+ # Move a key to another database.
298
+ #
299
+ # @example Move a key to another database
300
+ # redis.set "foo", "bar"
301
+ # # => "OK"
302
+ # redis.move "foo", 2
303
+ # # => true
304
+ # redis.exists "foo"
305
+ # # => false
306
+ # redis.select 2
307
+ # # => "OK"
308
+ # redis.exists "foo"
309
+ # # => true
310
+ # redis.get "foo"
311
+ # # => "bar"
312
+ #
313
+ # @param [String] key
314
+ # @param [Integer] db
315
+ # @return [Boolean] whether the key was moved or not
316
+ def move(key, db)
317
+ send_command([:move, key, db], &Boolify)
318
+ end
319
+
320
+ # Copy a value from one key to another.
321
+ #
322
+ # @example Copy a value to another key
323
+ # redis.set "foo", "value"
324
+ # # => "OK"
325
+ # redis.copy "foo", "bar"
326
+ # # => true
327
+ # redis.get "bar"
328
+ # # => "value"
329
+ #
330
+ # @example Copy a value to a key in another database
331
+ # redis.set "foo", "value"
332
+ # # => "OK"
333
+ # redis.copy "foo", "bar", db: 2
334
+ # # => true
335
+ # redis.select 2
336
+ # # => "OK"
337
+ # redis.get "bar"
338
+ # # => "value"
339
+ #
340
+ # @param [String] source
341
+ # @param [String] destination
342
+ # @param [Integer] db
343
+ # @param [Boolean] replace removes the `destination` key before copying value to it
344
+ # @return [Boolean] whether the key was copied or not
345
+ def copy(source, destination, db: nil, replace: false)
346
+ command = [:copy, source, destination]
347
+ command << "DB" << db if db
348
+ command << "REPLACE" if replace
349
+
350
+ send_command(command, &Boolify)
351
+ end
352
+
353
+ def object(*args)
354
+ send_command([:object] + args)
355
+ end
356
+
357
+ # Return a random key from the keyspace.
358
+ #
359
+ # @return [String]
360
+ def randomkey
361
+ send_command([:randomkey])
362
+ end
363
+
364
+ # Rename a key. If the new key already exists it is overwritten.
365
+ #
366
+ # @param [String] old_name
367
+ # @param [String] new_name
368
+ # @return [String] `OK`
369
+ def rename(old_name, new_name)
370
+ send_command([:rename, old_name, new_name])
371
+ end
372
+
373
+ # Rename a key, only if the new key does not exist.
374
+ #
375
+ # @param [String] old_name
376
+ # @param [String] new_name
377
+ # @return [Boolean] whether the key was renamed or not
378
+ def renamenx(old_name, new_name)
379
+ send_command([:renamenx, old_name, new_name], &Boolify)
380
+ end
381
+
382
+ # Sort the elements in a list, set or sorted set.
383
+ #
384
+ # @example Retrieve the first 2 elements from an alphabetically sorted "list"
385
+ # redis.sort("list", :order => "alpha", :limit => [0, 2])
386
+ # # => ["a", "b"]
387
+ # @example Store an alphabetically descending list in "target"
388
+ # redis.sort("list", :order => "desc alpha", :store => "target")
389
+ # # => 26
390
+ #
391
+ # @param [String] key
392
+ # @param [Hash] options
393
+ # - `:by => String`: use external key to sort elements by
394
+ # - `:limit => [offset, count]`: skip `offset` elements, return a maximum
395
+ # of `count` elements
396
+ # - `:get => [String, Array<String>]`: single key or array of keys to
397
+ # retrieve per element in the result
398
+ # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
399
+ # - `:store => String`: key to store the result at
400
+ #
401
+ # @return [Array<String>, Array<Array<String>>, Integer]
402
+ # - when `:get` is not specified, or holds a single element, an array of elements
403
+ # - when `:get` is specified, and holds more than one element, an array of
404
+ # elements where every element is an array with the result for every
405
+ # element specified in `:get`
406
+ # - when `:store` is specified, the number of elements in the stored result
407
+ def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
408
+ args = [:sort, key]
409
+ args << "BY" << by if by
410
+
411
+ if limit
412
+ args << "LIMIT"
413
+ args.concat(limit)
414
+ end
415
+
416
+ get = Array(get)
417
+ get.each do |item|
418
+ args << "GET" << item
419
+ end
420
+
421
+ args.concat(order.split(" ")) if order
422
+ args << "STORE" << store if store
423
+
424
+ send_command(args) do |reply|
425
+ if get.size > 1 && !store
426
+ reply.each_slice(get.size).to_a if reply
427
+ else
428
+ reply
429
+ end
430
+ end
431
+ end
432
+
433
+ # Determine the type stored at key.
434
+ #
435
+ # @param [String] key
436
+ # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
437
+ def type(key)
438
+ send_command([:type, key])
439
+ end
440
+
441
+ private
442
+
443
+ def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
444
+ # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
445
+
446
+ args << cursor
447
+ args << "MATCH" << match if match
448
+ args << "COUNT" << count if count
449
+ args << "TYPE" << type if type
450
+
451
+ send_command([command] + args, &block)
452
+ end
453
+ end
454
+ end
455
+ end