redis 4.4.0 → 4.8.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.
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
data/lib/redis.rb CHANGED
@@ -1,32 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "monitor"
4
- require_relative "redis/errors"
4
+ require "redis/errors"
5
+ require "redis/commands"
5
6
 
6
7
  class Redis
8
+ BASE_PATH = __dir__
9
+ @exists_returns_integer = true
10
+ @sadd_returns_boolean = true
11
+
12
+ Deprecated = Class.new(StandardError)
13
+
7
14
  class << self
8
15
  attr_reader :exists_returns_integer
16
+ attr_accessor :silence_deprecations, :raise_deprecations, :sadd_returns_boolean
9
17
 
10
18
  def exists_returns_integer=(value)
11
19
  unless value
12
- message = "`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly " \
20
+ deprecate!(
21
+ "`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly " \
13
22
  "disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use " \
14
23
  "`exists?` instead."
15
-
16
- ::Kernel.warn(message)
24
+ )
17
25
  end
18
26
 
19
27
  @exists_returns_integer = value
20
28
  end
21
29
 
22
- attr_writer :current
23
- end
30
+ def deprecate!(message)
31
+ unless silence_deprecations
32
+ if raise_deprecations
33
+ raise Deprecated, message
34
+ else
35
+ ::Kernel.warn(message)
36
+ end
37
+ end
38
+ end
24
39
 
25
- def self.current
26
- @current ||= Redis.new
40
+ def current
41
+ deprecate!("`Redis.current` is deprecated and will be removed in 5.0. (called from: #{caller(1, 1).first})")
42
+ @current ||= Redis.new
43
+ end
44
+
45
+ def current=(redis)
46
+ deprecate!("`Redis.current=` is deprecated and will be removed in 5.0. (called from: #{caller(1, 1).first})")
47
+ @current = redis
48
+ end
27
49
  end
28
50
 
29
- include MonitorMixin
51
+ include Commands
30
52
 
31
53
  # Create a new client instance
32
54
  #
@@ -53,6 +75,8 @@ class Redis
53
75
  # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
54
76
  # @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
55
77
  # @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
78
+ # @option options [String] :fixed_hostname Specify a FQDN if cluster mode enabled and
79
+ # client has to connect nodes via single endpoint with SSL/TLS
56
80
  # @option options [Class] :connector Class of custom connector
57
81
  #
58
82
  # @return [Redis] a new client instance
@@ -62,12 +86,7 @@ class Redis
62
86
  client = @cluster_mode ? Cluster : Client
63
87
  @original_client = @client = client.new(options)
64
88
  @queue = Hash.new { |h, k| h[k] = [] }
65
-
66
- super() # Monitor#initialize
67
- end
68
-
69
- def synchronize
70
- mon_synchronize { yield(@client) }
89
+ @monitor = Monitor.new
71
90
  end
72
91
 
73
92
  # Run code with the client reconnecting
@@ -93,37 +112,37 @@ class Redis
93
112
  end
94
113
  alias disconnect! close
95
114
 
96
- # Sends a command to Redis and returns its reply.
97
- #
98
- # Replies are converted to Ruby objects according to the RESP protocol, so
99
- # you can expect a Ruby array, integer or nil when Redis sends one. Higher
100
- # level transformations, such as converting an array of pairs into a Ruby
101
- # hash, are up to consumers.
102
- #
103
- # Redis error replies are raised as Ruby exceptions.
104
- def call(*command)
105
- synchronize do |client|
106
- client.call(command)
107
- end
115
+ def with
116
+ yield self
108
117
  end
109
118
 
110
- # Queues a command for pipelining.
119
+ # @deprecated Queues a command for pipelining.
111
120
  #
112
121
  # Commands in the queue are executed with the Redis#commit method.
113
122
  #
114
123
  # See http://redis.io/topics/pipelining for more details.
115
124
  #
116
125
  def queue(*command)
126
+ ::Redis.deprecate!(
127
+ "Redis#queue is deprecated and will be removed in Redis 5.0.0. Use Redis#pipelined instead." \
128
+ "(called from: #{caller(1, 1).first})"
129
+ )
130
+
117
131
  synchronize do
118
132
  @queue[Thread.current.object_id] << command
119
133
  end
120
134
  end
121
135
 
122
- # Sends all commands in the queue.
136
+ # @deprecated Sends all commands in the queue.
123
137
  #
124
138
  # See http://redis.io/topics/pipelining for more details.
125
139
  #
126
140
  def commit
141
+ ::Redis.deprecate!(
142
+ "Redis#commit is deprecated and will be removed in Redis 5.0.0. Use Redis#pipelined instead. " \
143
+ "(called from: #{Kernel.caller(1, 1).first})"
144
+ )
145
+
127
146
  synchronize do |client|
128
147
  begin
129
148
  pipeline = Pipeline.new(client)
@@ -142,3504 +161,141 @@ class Redis
142
161
  @client
143
162
  end
144
163
 
145
- # Authenticate to the server.
146
- #
147
- # @param [Array<String>] args includes both username and password
148
- # or only password
149
- # @return [String] `OK`
150
- # @see https://redis.io/commands/auth AUTH command
151
- def auth(*args)
152
- synchronize do |client|
153
- client.call([:auth, *args])
154
- end
155
- end
156
-
157
- # Change the selected database for the current connection.
158
- #
159
- # @param [Integer] db zero-based index of the DB to use (0 to 15)
160
- # @return [String] `OK`
161
- def select(db)
162
- synchronize do |client|
163
- client.db = db
164
- client.call([:select, db])
165
- end
166
- end
167
-
168
- # Ping the server.
169
- #
170
- # @param [optional, String] message
171
- # @return [String] `PONG`
172
- def ping(message = nil)
173
- synchronize do |client|
174
- client.call([:ping, message].compact)
175
- end
176
- end
177
-
178
- # Echo the given string.
179
- #
180
- # @param [String] value
181
- # @return [String]
182
- def echo(value)
183
- synchronize do |client|
184
- client.call([:echo, value])
164
+ def pipelined(&block)
165
+ deprecation_displayed = false
166
+ if block&.arity == 0
167
+ Pipeline.deprecation_warning("pipelined", Kernel.caller_locations(1, 5))
168
+ deprecation_displayed = true
185
169
  end
186
- end
187
170
 
188
- # Close the connection.
189
- #
190
- # @return [String] `OK`
191
- def quit
192
- synchronize do |client|
171
+ synchronize do |prior_client|
193
172
  begin
194
- client.call([:quit])
195
- rescue ConnectionError
173
+ pipeline = Pipeline.new(prior_client)
174
+ @client = deprecation_displayed ? pipeline : DeprecatedPipeline.new(pipeline)
175
+ pipelined_connection = PipelinedConnection.new(pipeline)
176
+ yield pipelined_connection
177
+ prior_client.call_pipeline(pipeline)
196
178
  ensure
197
- client.disconnect
179
+ @client = prior_client
198
180
  end
199
181
  end
200
182
  end
201
183
 
202
- # Asynchronously rewrite the append-only file.
203
- #
204
- # @return [String] `OK`
205
- def bgrewriteaof
206
- synchronize do |client|
207
- client.call([:bgrewriteaof])
208
- end
209
- end
210
-
211
- # Asynchronously save the dataset to disk.
212
- #
213
- # @return [String] `OK`
214
- def bgsave
215
- synchronize do |client|
216
- client.call([:bgsave])
217
- end
218
- end
219
-
220
- # Get or set server configuration parameters.
184
+ # Mark the start of a transaction block.
221
185
  #
222
- # @param [Symbol] action e.g. `:get`, `:set`, `:resetstat`
223
- # @return [String, Hash] string reply, or hash when retrieving more than one
224
- # property with `CONFIG GET`
225
- def config(action, *args)
226
- synchronize do |client|
227
- client.call([:config, action] + args) do |reply|
228
- if reply.is_a?(Array) && action == :get
229
- Hashify.call(reply)
230
- else
231
- reply
232
- end
233
- end
234
- end
235
- end
236
-
237
- # Manage client connections.
186
+ # Passing a block is optional.
238
187
  #
239
- # @param [String, Symbol] subcommand e.g. `kill`, `list`, `getname`, `setname`
240
- # @return [String, Hash] depends on subcommand
241
- def client(subcommand = nil, *args)
242
- synchronize do |client|
243
- client.call([:client, subcommand] + args) do |reply|
244
- if subcommand.to_s == "list"
245
- reply.lines.map do |line|
246
- entries = line.chomp.split(/[ =]/)
247
- Hash[entries.each_slice(2).to_a]
248
- end
249
- else
250
- reply
251
- end
252
- end
253
- end
254
- end
255
-
256
- # Return the number of keys in the selected database.
188
+ # @example With a block
189
+ # redis.multi do |multi|
190
+ # multi.set("key", "value")
191
+ # multi.incr("counter")
192
+ # end # => ["OK", 6]
257
193
  #
258
- # @return [Integer]
259
- def dbsize
260
- synchronize do |client|
261
- client.call([:dbsize])
262
- end
263
- end
264
-
265
- def debug(*args)
266
- synchronize do |client|
267
- client.call([:debug] + args)
268
- end
269
- end
270
-
271
- # Remove all keys from all databases.
194
+ # @example Without a block
195
+ # redis.multi
196
+ # # => "OK"
197
+ # redis.set("key", "value")
198
+ # # => "QUEUED"
199
+ # redis.incr("counter")
200
+ # # => "QUEUED"
201
+ # redis.exec
202
+ # # => ["OK", 6]
272
203
  #
273
- # @param [Hash] options
274
- # - `:async => Boolean`: async flush (default: false)
275
- # @return [String] `OK`
276
- def flushall(options = nil)
277
- synchronize do |client|
278
- if options && options[:async]
279
- client.call(%i[flushall async])
280
- else
281
- client.call([:flushall])
282
- end
283
- end
284
- end
285
-
286
- # Remove all keys from the current database.
204
+ # @yield [multi] the commands that are called inside this block are cached
205
+ # and written to the server upon returning from it
206
+ # @yieldparam [Redis] multi `self`
287
207
  #
288
- # @param [Hash] options
289
- # - `:async => Boolean`: async flush (default: false)
290
- # @return [String] `OK`
291
- def flushdb(options = nil)
292
- synchronize do |client|
293
- if options && options[:async]
294
- client.call(%i[flushdb async])
295
- else
296
- client.call([:flushdb])
297
- end
298
- end
299
- end
300
-
301
- # Get information and statistics about the server.
208
+ # @return [String, Array<...>]
209
+ # - when a block is not given, `OK`
210
+ # - when a block is given, an array with replies
302
211
  #
303
- # @param [String, Symbol] cmd e.g. "commandstats"
304
- # @return [Hash<String, String>]
305
- def info(cmd = nil)
306
- synchronize do |client|
307
- client.call([:info, cmd].compact) do |reply|
308
- if reply.is_a?(String)
309
- reply = HashifyInfo.call(reply)
310
-
311
- if cmd && cmd.to_s == "commandstats"
312
- # Extract nested hashes for INFO COMMANDSTATS
313
- reply = Hash[reply.map do |k, v|
314
- v = v.split(",").map { |e| e.split("=") }
315
- [k[/^cmdstat_(.*)$/, 1], Hash[v]]
316
- end]
317
- end
318
- end
319
-
320
- reply
212
+ # @see #watch
213
+ # @see #unwatch
214
+ def multi(&block)
215
+ if block_given?
216
+ deprecation_displayed = false
217
+ if block&.arity == 0
218
+ Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 5))
219
+ deprecation_displayed = true
321
220
  end
322
- end
323
- end
324
-
325
- # Get the UNIX time stamp of the last successful save to disk.
326
- #
327
- # @return [Integer]
328
- def lastsave
329
- synchronize do |client|
330
- client.call([:lastsave])
331
- end
332
- end
333
-
334
- # Listen for all requests received by the server in real time.
335
- #
336
- # There is no way to interrupt this command.
337
- #
338
- # @yield a block to be called for every line of output
339
- # @yieldparam [String] line timestamp and command that was executed
340
- def monitor(&block)
341
- synchronize do |client|
342
- client.call_loop([:monitor], &block)
343
- end
344
- end
345
-
346
- # Synchronously save the dataset to disk.
347
- #
348
- # @return [String]
349
- def save
350
- synchronize do |client|
351
- client.call([:save])
352
- end
353
- end
354
221
 
355
- # Synchronously save the dataset to disk and then shut down the server.
356
- def shutdown
357
- synchronize do |client|
358
- client.with_reconnect(false) do
222
+ synchronize do |prior_client|
359
223
  begin
360
- client.call([:shutdown])
361
- rescue ConnectionError
362
- # This means Redis has probably exited.
363
- nil
224
+ pipeline = Pipeline::Multi.new(prior_client)
225
+ @client = deprecation_displayed ? pipeline : DeprecatedMulti.new(pipeline)
226
+ pipelined_connection = PipelinedConnection.new(pipeline)
227
+ yield pipelined_connection
228
+ prior_client.call_pipeline(pipeline)
229
+ ensure
230
+ @client = prior_client
364
231
  end
365
232
  end
233
+ else
234
+ send_command([:multi])
366
235
  end
367
236
  end
368
237
 
369
- # Make the server a slave of another instance, or promote it as master.
370
- def slaveof(host, port)
371
- synchronize do |client|
372
- client.call([:slaveof, host, port])
373
- end
374
- end
375
-
376
- # Interact with the slowlog (get, len, reset)
377
- #
378
- # @param [String] subcommand e.g. `get`, `len`, `reset`
379
- # @param [Integer] length maximum number of entries to return
380
- # @return [Array<String>, Integer, String] depends on subcommand
381
- def slowlog(subcommand, length = nil)
382
- synchronize do |client|
383
- args = [:slowlog, subcommand]
384
- args << length if length
385
- client.call args
386
- end
387
- end
388
-
389
- # Internal command used for replication.
390
- def sync
391
- synchronize do |client|
392
- client.call([:sync])
393
- end
394
- end
395
-
396
- # Return the server time.
397
- #
398
- # @example
399
- # r.time # => [ 1333093196, 606806 ]
400
- #
401
- # @return [Array<Integer>] tuple of seconds since UNIX epoch and
402
- # microseconds in the current second
403
- def time
404
- synchronize do |client|
405
- client.call([:time]) do |reply|
406
- reply&.map(&:to_i)
407
- end
408
- end
409
- end
410
-
411
- # Remove the expiration from a key.
412
- #
413
- # @param [String] key
414
- # @return [Boolean] whether the timeout was removed or not
415
- def persist(key)
416
- synchronize do |client|
417
- client.call([:persist, key], &Boolify)
418
- end
419
- end
420
-
421
- # Set a key's time to live in seconds.
422
- #
423
- # @param [String] key
424
- # @param [Integer] seconds time to live
425
- # @return [Boolean] whether the timeout was set or not
426
- def expire(key, seconds)
427
- synchronize do |client|
428
- client.call([:expire, key, seconds], &Boolify)
429
- end
430
- end
431
-
432
- # Set the expiration for a key as a UNIX timestamp.
433
- #
434
- # @param [String] key
435
- # @param [Integer] unix_time expiry time specified as a UNIX timestamp
436
- # @return [Boolean] whether the timeout was set or not
437
- def expireat(key, unix_time)
438
- synchronize do |client|
439
- client.call([:expireat, key, unix_time], &Boolify)
440
- end
441
- end
442
-
443
- # Get the time to live (in seconds) for a key.
444
- #
445
- # @param [String] key
446
- # @return [Integer] remaining time to live in seconds.
447
- #
448
- # In Redis 2.6 or older the command returns -1 if the key does not exist or if
449
- # the key exist but has no associated expire.
450
- #
451
- # Starting with Redis 2.8 the return value in case of error changed:
452
- #
453
- # - The command returns -2 if the key does not exist.
454
- # - The command returns -1 if the key exists but has no associated expire.
455
- def ttl(key)
456
- synchronize do |client|
457
- client.call([:ttl, key])
458
- end
459
- end
460
-
461
- # Set a key's time to live in milliseconds.
462
- #
463
- # @param [String] key
464
- # @param [Integer] milliseconds time to live
465
- # @return [Boolean] whether the timeout was set or not
466
- def pexpire(key, milliseconds)
467
- synchronize do |client|
468
- client.call([:pexpire, key, milliseconds], &Boolify)
469
- end
470
- end
471
-
472
- # Set the expiration for a key as number of milliseconds from UNIX Epoch.
473
- #
474
- # @param [String] key
475
- # @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
476
- # @return [Boolean] whether the timeout was set or not
477
- def pexpireat(key, ms_unix_time)
478
- synchronize do |client|
479
- client.call([:pexpireat, key, ms_unix_time], &Boolify)
480
- end
481
- end
482
-
483
- # Get the time to live (in milliseconds) for a key.
484
- #
485
- # @param [String] key
486
- # @return [Integer] remaining time to live in milliseconds
487
- # In Redis 2.6 or older the command returns -1 if the key does not exist or if
488
- # the key exist but has no associated expire.
489
- #
490
- # Starting with Redis 2.8 the return value in case of error changed:
491
- #
492
- # - The command returns -2 if the key does not exist.
493
- # - The command returns -1 if the key exists but has no associated expire.
494
- def pttl(key)
495
- synchronize do |client|
496
- client.call([:pttl, key])
497
- end
238
+ def id
239
+ @original_client.id
498
240
  end
499
241
 
500
- # Return a serialized version of the value stored at a key.
501
- #
502
- # @param [String] key
503
- # @return [String] serialized_value
504
- def dump(key)
505
- synchronize do |client|
506
- client.call([:dump, key])
507
- end
242
+ def inspect
243
+ "#<Redis client v#{Redis::VERSION} for #{id}>"
508
244
  end
509
245
 
510
- # Create a key using the serialized value, previously obtained using DUMP.
511
- #
512
- # @param [String] key
513
- # @param [String] ttl
514
- # @param [String] serialized_value
515
- # @param [Hash] options
516
- # - `:replace => Boolean`: if false, raises an error if key already exists
517
- # @raise [Redis::CommandError]
518
- # @return [String] `"OK"`
519
- def restore(key, ttl, serialized_value, replace: nil)
520
- args = [:restore, key, ttl, serialized_value]
521
- args << 'REPLACE' if replace
522
-
523
- synchronize do |client|
524
- client.call(args)
525
- end
246
+ def dup
247
+ self.class.new(@options)
526
248
  end
527
249
 
528
- # Transfer a key from the connected instance to another instance.
529
- #
530
- # @param [String, Array<String>] key
531
- # @param [Hash] options
532
- # - `:host => String`: host of instance to migrate to
533
- # - `:port => Integer`: port of instance to migrate to
534
- # - `:db => Integer`: database to migrate to (default: same as source)
535
- # - `:timeout => Integer`: timeout (default: same as connection timeout)
536
- # - `:copy => Boolean`: Do not remove the key from the local instance.
537
- # - `:replace => Boolean`: Replace existing key on the remote instance.
538
- # @return [String] `"OK"`
539
- def migrate(key, options)
540
- args = [:migrate]
541
- args << (options[:host] || raise(':host not specified'))
542
- args << (options[:port] || raise(':port not specified'))
543
- args << (key.is_a?(String) ? key : '')
544
- args << (options[:db] || @client.db).to_i
545
- args << (options[:timeout] || @client.timeout).to_i
546
- args << 'COPY' if options[:copy]
547
- args << 'REPLACE' if options[:replace]
548
- args += ['KEYS', *key] if key.is_a?(Array)
250
+ def connection
251
+ return @original_client.connection_info if @cluster_mode
549
252
 
550
- synchronize { |client| client.call(args) }
253
+ {
254
+ host: @original_client.host,
255
+ port: @original_client.port,
256
+ db: @original_client.db,
257
+ id: @original_client.id,
258
+ location: @original_client.location
259
+ }
551
260
  end
552
261
 
553
- # Delete one or more keys.
554
- #
555
- # @param [String, Array<String>] keys
556
- # @return [Integer] number of keys that were deleted
557
- def del(*keys)
558
- keys.flatten!(1)
559
- return 0 if keys.empty?
560
-
561
- synchronize do |client|
562
- client.call([:del] + keys)
563
- end
564
- end
262
+ private
565
263
 
566
- # Unlink one or more keys.
567
- #
568
- # @param [String, Array<String>] keys
569
- # @return [Integer] number of keys that were unlinked
570
- def unlink(*keys)
571
- synchronize do |client|
572
- client.call([:unlink] + keys)
573
- end
264
+ def synchronize
265
+ @monitor.synchronize { yield(@client) }
574
266
  end
575
267
 
576
- # Determine how many of the keys exists.
577
- #
578
- # @param [String, Array<String>] keys
579
- # @return [Integer]
580
- def exists(*keys)
581
- if !Redis.exists_returns_integer && keys.size == 1
582
- if Redis.exists_returns_integer.nil?
583
- message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
584
- "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
585
- "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
586
- "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0. " \
587
- "(#{::Kernel.caller(1, 1).first})\n"
588
-
589
- ::Kernel.warn(message)
590
- end
591
-
592
- exists?(*keys)
593
- else
594
- _exists(*keys)
268
+ def send_command(command, &block)
269
+ @monitor.synchronize do
270
+ @client.call(command, &block)
595
271
  end
596
272
  end
597
273
 
598
- def _exists(*keys)
599
- synchronize do |client|
600
- client.call([:exists, *keys])
274
+ def send_blocking_command(command, timeout, &block)
275
+ @monitor.synchronize do
276
+ @client.call_with_timeout(command, timeout, &block)
601
277
  end
602
278
  end
603
279
 
604
- # Determine if any of the keys exists.
605
- #
606
- # @param [String, Array<String>] keys
607
- # @return [Boolean]
608
- def exists?(*keys)
609
- synchronize do |client|
610
- client.call([:exists, *keys]) do |value|
611
- value > 0
612
- end
613
- end
614
- end
280
+ def _subscription(method, timeout, channels, block)
281
+ return @client.call([method] + channels) if subscribed?
615
282
 
616
- # Find all keys matching the given pattern.
617
- #
618
- # @param [String] pattern
619
- # @return [Array<String>]
620
- def keys(pattern = "*")
621
- synchronize do |client|
622
- client.call([:keys, pattern]) do |reply|
623
- if reply.is_a?(String)
624
- reply.split(" ")
625
- else
626
- reply
627
- end
628
- end
629
- end
630
- end
631
-
632
- # Move a key to another database.
633
- #
634
- # @example Move a key to another database
635
- # redis.set "foo", "bar"
636
- # # => "OK"
637
- # redis.move "foo", 2
638
- # # => true
639
- # redis.exists "foo"
640
- # # => false
641
- # redis.select 2
642
- # # => "OK"
643
- # redis.exists "foo"
644
- # # => true
645
- # redis.get "foo"
646
- # # => "bar"
647
- #
648
- # @param [String] key
649
- # @param [Integer] db
650
- # @return [Boolean] whether the key was moved or not
651
- def move(key, db)
652
- synchronize do |client|
653
- client.call([:move, key, db], &Boolify)
654
- end
655
- end
656
-
657
- def object(*args)
658
- synchronize do |client|
659
- client.call([:object] + args)
660
- end
661
- end
662
-
663
- # Return a random key from the keyspace.
664
- #
665
- # @return [String]
666
- def randomkey
667
- synchronize do |client|
668
- client.call([:randomkey])
669
- end
670
- end
671
-
672
- # Rename a key. If the new key already exists it is overwritten.
673
- #
674
- # @param [String] old_name
675
- # @param [String] new_name
676
- # @return [String] `OK`
677
- def rename(old_name, new_name)
678
- synchronize do |client|
679
- client.call([:rename, old_name, new_name])
680
- end
681
- end
682
-
683
- # Rename a key, only if the new key does not exist.
684
- #
685
- # @param [String] old_name
686
- # @param [String] new_name
687
- # @return [Boolean] whether the key was renamed or not
688
- def renamenx(old_name, new_name)
689
- synchronize do |client|
690
- client.call([:renamenx, old_name, new_name], &Boolify)
691
- end
692
- end
693
-
694
- # Sort the elements in a list, set or sorted set.
695
- #
696
- # @example Retrieve the first 2 elements from an alphabetically sorted "list"
697
- # redis.sort("list", :order => "alpha", :limit => [0, 2])
698
- # # => ["a", "b"]
699
- # @example Store an alphabetically descending list in "target"
700
- # redis.sort("list", :order => "desc alpha", :store => "target")
701
- # # => 26
702
- #
703
- # @param [String] key
704
- # @param [Hash] options
705
- # - `:by => String`: use external key to sort elements by
706
- # - `:limit => [offset, count]`: skip `offset` elements, return a maximum
707
- # of `count` elements
708
- # - `:get => [String, Array<String>]`: single key or array of keys to
709
- # retrieve per element in the result
710
- # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
711
- # - `:store => String`: key to store the result at
712
- #
713
- # @return [Array<String>, Array<Array<String>>, Integer]
714
- # - when `:get` is not specified, or holds a single element, an array of elements
715
- # - when `:get` is specified, and holds more than one element, an array of
716
- # elements where every element is an array with the result for every
717
- # element specified in `:get`
718
- # - when `:store` is specified, the number of elements in the stored result
719
- def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
720
- args = [:sort, key]
721
- args << "BY" << by if by
722
-
723
- if limit
724
- args << "LIMIT"
725
- args.concat(limit)
726
- end
727
-
728
- get = Array(get)
729
- get.each do |item|
730
- args << "GET" << item
731
- end
732
-
733
- args.concat(order.split(" ")) if order
734
- args << "STORE" << store if store
735
-
736
- synchronize do |client|
737
- client.call(args) do |reply|
738
- if get.size > 1 && !store
739
- reply.each_slice(get.size).to_a if reply
740
- else
741
- reply
742
- end
743
- end
744
- end
745
- end
746
-
747
- # Determine the type stored at key.
748
- #
749
- # @param [String] key
750
- # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
751
- def type(key)
752
- synchronize do |client|
753
- client.call([:type, key])
754
- end
755
- end
756
-
757
- # Decrement the integer value of a key by one.
758
- #
759
- # @example
760
- # redis.decr("value")
761
- # # => 4
762
- #
763
- # @param [String] key
764
- # @return [Integer] value after decrementing it
765
- def decr(key)
766
- synchronize do |client|
767
- client.call([:decr, key])
768
- end
769
- end
770
-
771
- # Decrement the integer value of a key by the given number.
772
- #
773
- # @example
774
- # redis.decrby("value", 5)
775
- # # => 0
776
- #
777
- # @param [String] key
778
- # @param [Integer] decrement
779
- # @return [Integer] value after decrementing it
780
- def decrby(key, decrement)
781
- synchronize do |client|
782
- client.call([:decrby, key, decrement])
783
- end
784
- end
785
-
786
- # Increment the integer value of a key by one.
787
- #
788
- # @example
789
- # redis.incr("value")
790
- # # => 6
791
- #
792
- # @param [String] key
793
- # @return [Integer] value after incrementing it
794
- def incr(key)
795
- synchronize do |client|
796
- client.call([:incr, key])
797
- end
798
- end
799
-
800
- # Increment the integer value of a key by the given integer number.
801
- #
802
- # @example
803
- # redis.incrby("value", 5)
804
- # # => 10
805
- #
806
- # @param [String] key
807
- # @param [Integer] increment
808
- # @return [Integer] value after incrementing it
809
- def incrby(key, increment)
810
- synchronize do |client|
811
- client.call([:incrby, key, increment])
812
- end
813
- end
814
-
815
- # Increment the numeric value of a key by the given float number.
816
- #
817
- # @example
818
- # redis.incrbyfloat("value", 1.23)
819
- # # => 1.23
820
- #
821
- # @param [String] key
822
- # @param [Float] increment
823
- # @return [Float] value after incrementing it
824
- def incrbyfloat(key, increment)
825
- synchronize do |client|
826
- client.call([:incrbyfloat, key, increment], &Floatify)
827
- end
828
- end
829
-
830
- # Set the string value of a key.
831
- #
832
- # @param [String] key
833
- # @param [String] value
834
- # @param [Hash] options
835
- # - `:ex => Integer`: Set the specified expire time, in seconds.
836
- # - `:px => Integer`: Set the specified expire time, in milliseconds.
837
- # - `:nx => true`: Only set the key if it does not already exist.
838
- # - `:xx => true`: Only set the key if it already exist.
839
- # - `:keepttl => true`: Retain the time to live associated with the key.
840
- # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
841
- def set(key, value, ex: nil, px: nil, nx: nil, xx: nil, keepttl: nil)
842
- args = [:set, key, value.to_s]
843
- args << "EX" << ex if ex
844
- args << "PX" << px if px
845
- args << "NX" if nx
846
- args << "XX" if xx
847
- args << "KEEPTTL" if keepttl
848
-
849
- synchronize do |client|
850
- if nx || xx
851
- client.call(args, &BoolifySet)
852
- else
853
- client.call(args)
854
- end
855
- end
856
- end
857
-
858
- # Set the time to live in seconds of a key.
859
- #
860
- # @param [String] key
861
- # @param [Integer] ttl
862
- # @param [String] value
863
- # @return [String] `"OK"`
864
- def setex(key, ttl, value)
865
- synchronize do |client|
866
- client.call([:setex, key, ttl, value.to_s])
867
- end
868
- end
869
-
870
- # Set the time to live in milliseconds of a key.
871
- #
872
- # @param [String] key
873
- # @param [Integer] ttl
874
- # @param [String] value
875
- # @return [String] `"OK"`
876
- def psetex(key, ttl, value)
877
- synchronize do |client|
878
- client.call([:psetex, key, ttl, value.to_s])
879
- end
880
- end
881
-
882
- # Set the value of a key, only if the key does not exist.
883
- #
884
- # @param [String] key
885
- # @param [String] value
886
- # @return [Boolean] whether the key was set or not
887
- def setnx(key, value)
888
- synchronize do |client|
889
- client.call([:setnx, key, value.to_s], &Boolify)
890
- end
891
- end
892
-
893
- # Set one or more values.
894
- #
895
- # @example
896
- # redis.mset("key1", "v1", "key2", "v2")
897
- # # => "OK"
898
- #
899
- # @param [Array<String>] args array of keys and values
900
- # @return [String] `"OK"`
901
- #
902
- # @see #mapped_mset
903
- def mset(*args)
904
- synchronize do |client|
905
- client.call([:mset] + args)
906
- end
907
- end
908
-
909
- # Set one or more values.
910
- #
911
- # @example
912
- # redis.mapped_mset({ "f1" => "v1", "f2" => "v2" })
913
- # # => "OK"
914
- #
915
- # @param [Hash] hash keys mapping to values
916
- # @return [String] `"OK"`
917
- #
918
- # @see #mset
919
- def mapped_mset(hash)
920
- mset(hash.to_a.flatten)
921
- end
922
-
923
- # Set one or more values, only if none of the keys exist.
924
- #
925
- # @example
926
- # redis.msetnx("key1", "v1", "key2", "v2")
927
- # # => true
928
- #
929
- # @param [Array<String>] args array of keys and values
930
- # @return [Boolean] whether or not all values were set
931
- #
932
- # @see #mapped_msetnx
933
- def msetnx(*args)
934
- synchronize do |client|
935
- client.call([:msetnx, *args], &Boolify)
936
- end
937
- end
938
-
939
- # Set one or more values, only if none of the keys exist.
940
- #
941
- # @example
942
- # redis.mapped_msetnx({ "key1" => "v1", "key2" => "v2" })
943
- # # => true
944
- #
945
- # @param [Hash] hash keys mapping to values
946
- # @return [Boolean] whether or not all values were set
947
- #
948
- # @see #msetnx
949
- def mapped_msetnx(hash)
950
- msetnx(hash.to_a.flatten)
951
- end
952
-
953
- # Get the value of a key.
954
- #
955
- # @param [String] key
956
- # @return [String]
957
- def get(key)
958
- synchronize do |client|
959
- client.call([:get, key])
960
- end
961
- end
962
-
963
- # Get the values of all the given keys.
964
- #
965
- # @example
966
- # redis.mget("key1", "key2")
967
- # # => ["v1", "v2"]
968
- #
969
- # @param [Array<String>] keys
970
- # @return [Array<String>] an array of values for the specified keys
971
- #
972
- # @see #mapped_mget
973
- def mget(*keys, &blk)
974
- synchronize do |client|
975
- client.call([:mget, *keys], &blk)
976
- end
977
- end
978
-
979
- # Get the values of all the given keys.
980
- #
981
- # @example
982
- # redis.mapped_mget("key1", "key2")
983
- # # => { "key1" => "v1", "key2" => "v2" }
984
- #
985
- # @param [Array<String>] keys array of keys
986
- # @return [Hash] a hash mapping the specified keys to their values
987
- #
988
- # @see #mget
989
- def mapped_mget(*keys)
990
- mget(*keys) do |reply|
991
- if reply.is_a?(Array)
992
- Hash[keys.zip(reply)]
993
- else
994
- reply
995
- end
996
- end
997
- end
998
-
999
- # Overwrite part of a string at key starting at the specified offset.
1000
- #
1001
- # @param [String] key
1002
- # @param [Integer] offset byte offset
1003
- # @param [String] value
1004
- # @return [Integer] length of the string after it was modified
1005
- def setrange(key, offset, value)
1006
- synchronize do |client|
1007
- client.call([:setrange, key, offset, value.to_s])
1008
- end
1009
- end
1010
-
1011
- # Get a substring of the string stored at a key.
1012
- #
1013
- # @param [String] key
1014
- # @param [Integer] start zero-based start offset
1015
- # @param [Integer] stop zero-based end offset. Use -1 for representing
1016
- # the end of the string
1017
- # @return [Integer] `0` or `1`
1018
- def getrange(key, start, stop)
1019
- synchronize do |client|
1020
- client.call([:getrange, key, start, stop])
1021
- end
1022
- end
1023
-
1024
- # Sets or clears the bit at offset in the string value stored at key.
1025
- #
1026
- # @param [String] key
1027
- # @param [Integer] offset bit offset
1028
- # @param [Integer] value bit value `0` or `1`
1029
- # @return [Integer] the original bit value stored at `offset`
1030
- def setbit(key, offset, value)
1031
- synchronize do |client|
1032
- client.call([:setbit, key, offset, value])
1033
- end
1034
- end
1035
-
1036
- # Returns the bit value at offset in the string value stored at key.
1037
- #
1038
- # @param [String] key
1039
- # @param [Integer] offset bit offset
1040
- # @return [Integer] `0` or `1`
1041
- def getbit(key, offset)
1042
- synchronize do |client|
1043
- client.call([:getbit, key, offset])
1044
- end
1045
- end
1046
-
1047
- # Append a value to a key.
1048
- #
1049
- # @param [String] key
1050
- # @param [String] value value to append
1051
- # @return [Integer] length of the string after appending
1052
- def append(key, value)
1053
- synchronize do |client|
1054
- client.call([:append, key, value])
1055
- end
1056
- end
1057
-
1058
- # Count the number of set bits in a range of the string value stored at key.
1059
- #
1060
- # @param [String] key
1061
- # @param [Integer] start start index
1062
- # @param [Integer] stop stop index
1063
- # @return [Integer] the number of bits set to 1
1064
- def bitcount(key, start = 0, stop = -1)
1065
- synchronize do |client|
1066
- client.call([:bitcount, key, start, stop])
1067
- end
1068
- end
1069
-
1070
- # Perform a bitwise operation between strings and store the resulting string in a key.
1071
- #
1072
- # @param [String] operation e.g. `and`, `or`, `xor`, `not`
1073
- # @param [String] destkey destination key
1074
- # @param [String, Array<String>] keys one or more source keys to perform `operation`
1075
- # @return [Integer] the length of the string stored in `destkey`
1076
- def bitop(operation, destkey, *keys)
1077
- synchronize do |client|
1078
- client.call([:bitop, operation, destkey, *keys])
1079
- end
1080
- end
1081
-
1082
- # Return the position of the first bit set to 1 or 0 in a string.
1083
- #
1084
- # @param [String] key
1085
- # @param [Integer] bit whether to look for the first 1 or 0 bit
1086
- # @param [Integer] start start index
1087
- # @param [Integer] stop stop index
1088
- # @return [Integer] the position of the first 1/0 bit.
1089
- # -1 if looking for 1 and it is not found or start and stop are given.
1090
- def bitpos(key, bit, start = nil, stop = nil)
1091
- raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
1092
-
1093
- synchronize do |client|
1094
- command = [:bitpos, key, bit]
1095
- command << start if start
1096
- command << stop if stop
1097
- client.call(command)
1098
- end
1099
- end
1100
-
1101
- # Set the string value of a key and return its old value.
1102
- #
1103
- # @param [String] key
1104
- # @param [String] value value to replace the current value with
1105
- # @return [String] the old value stored in the key, or `nil` if the key
1106
- # did not exist
1107
- def getset(key, value)
1108
- synchronize do |client|
1109
- client.call([:getset, key, value.to_s])
1110
- end
1111
- end
1112
-
1113
- # Get the length of the value stored in a key.
1114
- #
1115
- # @param [String] key
1116
- # @return [Integer] the length of the value stored in the key, or 0
1117
- # if the key does not exist
1118
- def strlen(key)
1119
- synchronize do |client|
1120
- client.call([:strlen, key])
1121
- end
1122
- end
1123
-
1124
- # Get the length of a list.
1125
- #
1126
- # @param [String] key
1127
- # @return [Integer]
1128
- def llen(key)
1129
- synchronize do |client|
1130
- client.call([:llen, key])
1131
- end
1132
- end
1133
-
1134
- # Prepend one or more values to a list, creating the list if it doesn't exist
1135
- #
1136
- # @param [String] key
1137
- # @param [String, Array<String>] value string value, or array of string values to push
1138
- # @return [Integer] the length of the list after the push operation
1139
- def lpush(key, value)
1140
- synchronize do |client|
1141
- client.call([:lpush, key, value])
1142
- end
1143
- end
1144
-
1145
- # Prepend a value to a list, only if the list exists.
1146
- #
1147
- # @param [String] key
1148
- # @param [String] value
1149
- # @return [Integer] the length of the list after the push operation
1150
- def lpushx(key, value)
1151
- synchronize do |client|
1152
- client.call([:lpushx, key, value])
1153
- end
1154
- end
1155
-
1156
- # Append one or more values to a list, creating the list if it doesn't exist
1157
- #
1158
- # @param [String] key
1159
- # @param [String, Array<String>] value string value, or array of string values to push
1160
- # @return [Integer] the length of the list after the push operation
1161
- def rpush(key, value)
1162
- synchronize do |client|
1163
- client.call([:rpush, key, value])
1164
- end
1165
- end
1166
-
1167
- # Append a value to a list, only if the list exists.
1168
- #
1169
- # @param [String] key
1170
- # @param [String] value
1171
- # @return [Integer] the length of the list after the push operation
1172
- def rpushx(key, value)
1173
- synchronize do |client|
1174
- client.call([:rpushx, key, value])
1175
- end
1176
- end
1177
-
1178
- # Remove and get the first elements in a list.
1179
- #
1180
- # @param [String] key
1181
- # @param [Integer] count number of elements to remove
1182
- # @return [String, Array<String>] the values of the first elements
1183
- def lpop(key, count = nil)
1184
- synchronize do |client|
1185
- command = [:lpop, key]
1186
- command << count if count
1187
- client.call(command)
1188
- end
1189
- end
1190
-
1191
- # Remove and get the last elements in a list.
1192
- #
1193
- # @param [String] key
1194
- # @param [Integer] count number of elements to remove
1195
- # @return [String, Array<String>] the values of the last elements
1196
- def rpop(key, count = nil)
1197
- synchronize do |client|
1198
- command = [:rpop, key]
1199
- command << count if count
1200
- client.call(command)
1201
- end
1202
- end
1203
-
1204
- # Remove the last element in a list, append it to another list and return it.
1205
- #
1206
- # @param [String] source source key
1207
- # @param [String] destination destination key
1208
- # @return [nil, String] the element, or nil when the source key does not exist
1209
- def rpoplpush(source, destination)
1210
- synchronize do |client|
1211
- client.call([:rpoplpush, source, destination])
1212
- end
1213
- end
1214
-
1215
- def _bpop(cmd, args, &blk)
1216
- timeout = if args.last.is_a?(Hash)
1217
- options = args.pop
1218
- options[:timeout]
1219
- elsif args.last.respond_to?(:to_int)
1220
- # Issue deprecation notice in obnoxious mode...
1221
- args.pop.to_int
1222
- end
1223
-
1224
- timeout ||= 0
1225
-
1226
- if args.size > 1
1227
- # Issue deprecation notice in obnoxious mode...
1228
- end
1229
-
1230
- keys = args.flatten
1231
-
1232
- synchronize do |client|
1233
- command = [cmd, keys, timeout]
1234
- timeout += client.timeout if timeout > 0
1235
- client.call_with_timeout(command, timeout, &blk)
1236
- end
1237
- end
1238
-
1239
- # Remove and get the first element in a list, or block until one is available.
1240
- #
1241
- # @example With timeout
1242
- # list, element = redis.blpop("list", :timeout => 5)
1243
- # # => nil on timeout
1244
- # # => ["list", "element"] on success
1245
- # @example Without timeout
1246
- # list, element = redis.blpop("list")
1247
- # # => ["list", "element"]
1248
- # @example Blocking pop on multiple lists
1249
- # list, element = redis.blpop(["list", "another_list"])
1250
- # # => ["list", "element"]
1251
- #
1252
- # @param [String, Array<String>] keys one or more keys to perform the
1253
- # blocking pop on
1254
- # @param [Hash] options
1255
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1256
- #
1257
- # @return [nil, [String, String]]
1258
- # - `nil` when the operation timed out
1259
- # - tuple of the list that was popped from and element was popped otherwise
1260
- def blpop(*args)
1261
- _bpop(:blpop, args)
1262
- end
1263
-
1264
- # Remove and get the last element in a list, or block until one is available.
1265
- #
1266
- # @param [String, Array<String>] keys one or more keys to perform the
1267
- # blocking pop on
1268
- # @param [Hash] options
1269
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1270
- #
1271
- # @return [nil, [String, String]]
1272
- # - `nil` when the operation timed out
1273
- # - tuple of the list that was popped from and element was popped otherwise
1274
- #
1275
- # @see #blpop
1276
- def brpop(*args)
1277
- _bpop(:brpop, args)
1278
- end
1279
-
1280
- # Pop a value from a list, push it to another list and return it; or block
1281
- # until one is available.
1282
- #
1283
- # @param [String] source source key
1284
- # @param [String] destination destination key
1285
- # @param [Hash] options
1286
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1287
- #
1288
- # @return [nil, String]
1289
- # - `nil` when the operation timed out
1290
- # - the element was popped and pushed otherwise
1291
- def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
1292
- synchronize do |client|
1293
- command = [:brpoplpush, source, destination, timeout]
1294
- timeout += client.timeout if timeout > 0
1295
- client.call_with_timeout(command, timeout)
1296
- end
1297
- end
1298
-
1299
- # Get an element from a list by its index.
1300
- #
1301
- # @param [String] key
1302
- # @param [Integer] index
1303
- # @return [String]
1304
- def lindex(key, index)
1305
- synchronize do |client|
1306
- client.call([:lindex, key, index])
1307
- end
1308
- end
1309
-
1310
- # Insert an element before or after another element in a list.
1311
- #
1312
- # @param [String] key
1313
- # @param [String, Symbol] where `BEFORE` or `AFTER`
1314
- # @param [String] pivot reference element
1315
- # @param [String] value
1316
- # @return [Integer] length of the list after the insert operation, or `-1`
1317
- # when the element `pivot` was not found
1318
- def linsert(key, where, pivot, value)
1319
- synchronize do |client|
1320
- client.call([:linsert, key, where, pivot, value])
1321
- end
1322
- end
1323
-
1324
- # Get a range of elements from a list.
1325
- #
1326
- # @param [String] key
1327
- # @param [Integer] start start index
1328
- # @param [Integer] stop stop index
1329
- # @return [Array<String>]
1330
- def lrange(key, start, stop)
1331
- synchronize do |client|
1332
- client.call([:lrange, key, start, stop])
1333
- end
1334
- end
1335
-
1336
- # Remove elements from a list.
1337
- #
1338
- # @param [String] key
1339
- # @param [Integer] count number of elements to remove. Use a positive
1340
- # value to remove the first `count` occurrences of `value`. A negative
1341
- # value to remove the last `count` occurrences of `value`. Or zero, to
1342
- # remove all occurrences of `value` from the list.
1343
- # @param [String] value
1344
- # @return [Integer] the number of removed elements
1345
- def lrem(key, count, value)
1346
- synchronize do |client|
1347
- client.call([:lrem, key, count, value])
1348
- end
1349
- end
1350
-
1351
- # Set the value of an element in a list by its index.
1352
- #
1353
- # @param [String] key
1354
- # @param [Integer] index
1355
- # @param [String] value
1356
- # @return [String] `OK`
1357
- def lset(key, index, value)
1358
- synchronize do |client|
1359
- client.call([:lset, key, index, value])
1360
- end
1361
- end
1362
-
1363
- # Trim a list to the specified range.
1364
- #
1365
- # @param [String] key
1366
- # @param [Integer] start start index
1367
- # @param [Integer] stop stop index
1368
- # @return [String] `OK`
1369
- def ltrim(key, start, stop)
1370
- synchronize do |client|
1371
- client.call([:ltrim, key, start, stop])
1372
- end
1373
- end
1374
-
1375
- # Get the number of members in a set.
1376
- #
1377
- # @param [String] key
1378
- # @return [Integer]
1379
- def scard(key)
1380
- synchronize do |client|
1381
- client.call([:scard, key])
1382
- end
1383
- end
1384
-
1385
- # Add one or more members to a set.
1386
- #
1387
- # @param [String] key
1388
- # @param [String, Array<String>] member one member, or array of members
1389
- # @return [Boolean, Integer] `Boolean` when a single member is specified,
1390
- # holding whether or not adding the member succeeded, or `Integer` when an
1391
- # array of members is specified, holding the number of members that were
1392
- # successfully added
1393
- def sadd(key, member)
1394
- synchronize do |client|
1395
- client.call([:sadd, key, member]) do |reply|
1396
- if member.is_a? Array
1397
- # Variadic: return integer
1398
- reply
1399
- else
1400
- # Single argument: return boolean
1401
- Boolify.call(reply)
1402
- end
1403
- end
1404
- end
1405
- end
1406
-
1407
- # Remove one or more members from a set.
1408
- #
1409
- # @param [String] key
1410
- # @param [String, Array<String>] member one member, or array of members
1411
- # @return [Boolean, Integer] `Boolean` when a single member is specified,
1412
- # holding whether or not removing the member succeeded, or `Integer` when an
1413
- # array of members is specified, holding the number of members that were
1414
- # successfully removed
1415
- def srem(key, member)
1416
- synchronize do |client|
1417
- client.call([:srem, key, member]) do |reply|
1418
- if member.is_a? Array
1419
- # Variadic: return integer
1420
- reply
1421
- else
1422
- # Single argument: return boolean
1423
- Boolify.call(reply)
1424
- end
1425
- end
1426
- end
1427
- end
1428
-
1429
- # Remove and return one or more random member from a set.
1430
- #
1431
- # @param [String] key
1432
- # @return [String]
1433
- # @param [Integer] count
1434
- def spop(key, count = nil)
1435
- synchronize do |client|
1436
- if count.nil?
1437
- client.call([:spop, key])
1438
- else
1439
- client.call([:spop, key, count])
1440
- end
1441
- end
1442
- end
1443
-
1444
- # Get one or more random members from a set.
1445
- #
1446
- # @param [String] key
1447
- # @param [Integer] count
1448
- # @return [String]
1449
- def srandmember(key, count = nil)
1450
- synchronize do |client|
1451
- if count.nil?
1452
- client.call([:srandmember, key])
1453
- else
1454
- client.call([:srandmember, key, count])
1455
- end
1456
- end
1457
- end
1458
-
1459
- # Move a member from one set to another.
1460
- #
1461
- # @param [String] source source key
1462
- # @param [String] destination destination key
1463
- # @param [String] member member to move from `source` to `destination`
1464
- # @return [Boolean]
1465
- def smove(source, destination, member)
1466
- synchronize do |client|
1467
- client.call([:smove, source, destination, member], &Boolify)
1468
- end
1469
- end
1470
-
1471
- # Determine if a given value is a member of a set.
1472
- #
1473
- # @param [String] key
1474
- # @param [String] member
1475
- # @return [Boolean]
1476
- def sismember(key, member)
1477
- synchronize do |client|
1478
- client.call([:sismember, key, member], &Boolify)
1479
- end
1480
- end
1481
-
1482
- # Get all the members in a set.
1483
- #
1484
- # @param [String] key
1485
- # @return [Array<String>]
1486
- def smembers(key)
1487
- synchronize do |client|
1488
- client.call([:smembers, key])
1489
- end
1490
- end
1491
-
1492
- # Subtract multiple sets.
1493
- #
1494
- # @param [String, Array<String>] keys keys pointing to sets to subtract
1495
- # @return [Array<String>] members in the difference
1496
- def sdiff(*keys)
1497
- synchronize do |client|
1498
- client.call([:sdiff, *keys])
1499
- end
1500
- end
1501
-
1502
- # Subtract multiple sets and store the resulting set in a key.
1503
- #
1504
- # @param [String] destination destination key
1505
- # @param [String, Array<String>] keys keys pointing to sets to subtract
1506
- # @return [Integer] number of elements in the resulting set
1507
- def sdiffstore(destination, *keys)
1508
- synchronize do |client|
1509
- client.call([:sdiffstore, destination, *keys])
1510
- end
1511
- end
1512
-
1513
- # Intersect multiple sets.
1514
- #
1515
- # @param [String, Array<String>] keys keys pointing to sets to intersect
1516
- # @return [Array<String>] members in the intersection
1517
- def sinter(*keys)
1518
- synchronize do |client|
1519
- client.call([:sinter, *keys])
1520
- end
1521
- end
1522
-
1523
- # Intersect multiple sets and store the resulting set in a key.
1524
- #
1525
- # @param [String] destination destination key
1526
- # @param [String, Array<String>] keys keys pointing to sets to intersect
1527
- # @return [Integer] number of elements in the resulting set
1528
- def sinterstore(destination, *keys)
1529
- synchronize do |client|
1530
- client.call([:sinterstore, destination, *keys])
1531
- end
1532
- end
1533
-
1534
- # Add multiple sets.
1535
- #
1536
- # @param [String, Array<String>] keys keys pointing to sets to unify
1537
- # @return [Array<String>] members in the union
1538
- def sunion(*keys)
1539
- synchronize do |client|
1540
- client.call([:sunion, *keys])
1541
- end
1542
- end
1543
-
1544
- # Add multiple sets and store the resulting set in a key.
1545
- #
1546
- # @param [String] destination destination key
1547
- # @param [String, Array<String>] keys keys pointing to sets to unify
1548
- # @return [Integer] number of elements in the resulting set
1549
- def sunionstore(destination, *keys)
1550
- synchronize do |client|
1551
- client.call([:sunionstore, destination, *keys])
1552
- end
1553
- end
1554
-
1555
- # Get the number of members in a sorted set.
1556
- #
1557
- # @example
1558
- # redis.zcard("zset")
1559
- # # => 4
1560
- #
1561
- # @param [String] key
1562
- # @return [Integer]
1563
- def zcard(key)
1564
- synchronize do |client|
1565
- client.call([:zcard, key])
1566
- end
1567
- end
1568
-
1569
- # Add one or more members to a sorted set, or update the score for members
1570
- # that already exist.
1571
- #
1572
- # @example Add a single `[score, member]` pair to a sorted set
1573
- # redis.zadd("zset", 32.0, "member")
1574
- # @example Add an array of `[score, member]` pairs to a sorted set
1575
- # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]])
1576
- #
1577
- # @param [String] key
1578
- # @param [[Float, String], Array<[Float, String]>] args
1579
- # - a single `[score, member]` pair
1580
- # - an array of `[score, member]` pairs
1581
- # @param [Hash] options
1582
- # - `:xx => true`: Only update elements that already exist (never
1583
- # add elements)
1584
- # - `:nx => true`: Don't update already existing elements (always
1585
- # add new elements)
1586
- # - `:ch => true`: Modify the return value from the number of new
1587
- # elements added, to the total number of elements changed (CH is an
1588
- # abbreviation of changed); changed elements are new elements added
1589
- # and elements already existing for which the score was updated
1590
- # - `:incr => true`: When this option is specified ZADD acts like
1591
- # ZINCRBY; only one score-element pair can be specified in this mode
1592
- #
1593
- # @return [Boolean, Integer, Float]
1594
- # - `Boolean` when a single pair is specified, holding whether or not it was
1595
- # **added** to the sorted set.
1596
- # - `Integer` when an array of pairs is specified, holding the number of
1597
- # pairs that were **added** to the sorted set.
1598
- # - `Float` when option :incr is specified, holding the score of the member
1599
- # after incrementing it.
1600
- def zadd(key, *args, nx: nil, xx: nil, ch: nil, incr: nil)
1601
- command = [:zadd, key]
1602
- command << "NX" if nx
1603
- command << "XX" if xx
1604
- command << "CH" if ch
1605
- command << "INCR" if incr
1606
-
1607
- synchronize do |client|
1608
- if args.size == 1 && args[0].is_a?(Array)
1609
- # Variadic: return float if INCR, integer if !INCR
1610
- client.call(command + args[0], &(incr ? Floatify : nil))
1611
- elsif args.size == 2
1612
- # Single pair: return float if INCR, boolean if !INCR
1613
- client.call(command + args, &(incr ? Floatify : Boolify))
1614
- else
1615
- raise ArgumentError, "wrong number of arguments"
1616
- end
1617
- end
1618
- end
1619
-
1620
- # Increment the score of a member in a sorted set.
1621
- #
1622
- # @example
1623
- # redis.zincrby("zset", 32.0, "a")
1624
- # # => 64.0
1625
- #
1626
- # @param [String] key
1627
- # @param [Float] increment
1628
- # @param [String] member
1629
- # @return [Float] score of the member after incrementing it
1630
- def zincrby(key, increment, member)
1631
- synchronize do |client|
1632
- client.call([:zincrby, key, increment, member], &Floatify)
1633
- end
1634
- end
1635
-
1636
- # Remove one or more members from a sorted set.
1637
- #
1638
- # @example Remove a single member from a sorted set
1639
- # redis.zrem("zset", "a")
1640
- # @example Remove an array of members from a sorted set
1641
- # redis.zrem("zset", ["a", "b"])
1642
- #
1643
- # @param [String] key
1644
- # @param [String, Array<String>] member
1645
- # - a single member
1646
- # - an array of members
1647
- #
1648
- # @return [Boolean, Integer]
1649
- # - `Boolean` when a single member is specified, holding whether or not it
1650
- # was removed from the sorted set
1651
- # - `Integer` when an array of pairs is specified, holding the number of
1652
- # members that were removed to the sorted set
1653
- def zrem(key, member)
1654
- synchronize do |client|
1655
- client.call([:zrem, key, member]) do |reply|
1656
- if member.is_a? Array
1657
- # Variadic: return integer
1658
- reply
1659
- else
1660
- # Single argument: return boolean
1661
- Boolify.call(reply)
1662
- end
1663
- end
1664
- end
1665
- end
1666
-
1667
- # Removes and returns up to count members with the highest scores in the sorted set stored at key.
1668
- #
1669
- # @example Popping a member
1670
- # redis.zpopmax('zset')
1671
- # #=> ['b', 2.0]
1672
- # @example With count option
1673
- # redis.zpopmax('zset', 2)
1674
- # #=> [['b', 2.0], ['a', 1.0]]
1675
- #
1676
- # @params key [String] a key of the sorted set
1677
- # @params count [Integer] a number of members
1678
- #
1679
- # @return [Array<String, Float>] element and score pair if count is not specified
1680
- # @return [Array<Array<String, Float>>] list of popped elements and scores
1681
- def zpopmax(key, count = nil)
1682
- synchronize do |client|
1683
- members = client.call([:zpopmax, key, count].compact, &FloatifyPairs)
1684
- count.to_i > 1 ? members : members.first
1685
- end
1686
- end
1687
-
1688
- # Removes and returns up to count members with the lowest scores in the sorted set stored at key.
1689
- #
1690
- # @example Popping a member
1691
- # redis.zpopmin('zset')
1692
- # #=> ['a', 1.0]
1693
- # @example With count option
1694
- # redis.zpopmin('zset', 2)
1695
- # #=> [['a', 1.0], ['b', 2.0]]
1696
- #
1697
- # @params key [String] a key of the sorted set
1698
- # @params count [Integer] a number of members
1699
- #
1700
- # @return [Array<String, Float>] element and score pair if count is not specified
1701
- # @return [Array<Array<String, Float>>] list of popped elements and scores
1702
- def zpopmin(key, count = nil)
1703
- synchronize do |client|
1704
- members = client.call([:zpopmin, key, count].compact, &FloatifyPairs)
1705
- count.to_i > 1 ? members : members.first
1706
- end
1707
- end
1708
-
1709
- # Removes and returns up to count members with the highest scores in the sorted set stored at keys,
1710
- # or block until one is available.
1711
- #
1712
- # @example Popping a member from a sorted set
1713
- # redis.bzpopmax('zset', 1)
1714
- # #=> ['zset', 'b', 2.0]
1715
- # @example Popping a member from multiple sorted sets
1716
- # redis.bzpopmax('zset1', 'zset2', 1)
1717
- # #=> ['zset1', 'b', 2.0]
1718
- #
1719
- # @params keys [Array<String>] one or multiple keys of the sorted sets
1720
- # @params timeout [Integer] the maximum number of seconds to block
1721
- #
1722
- # @return [Array<String, String, Float>] a touple of key, member and score
1723
- # @return [nil] when no element could be popped and the timeout expired
1724
- def bzpopmax(*args)
1725
- _bpop(:bzpopmax, args) do |reply|
1726
- reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
1727
- end
1728
- end
1729
-
1730
- # Removes and returns up to count members with the lowest scores in the sorted set stored at keys,
1731
- # or block until one is available.
1732
- #
1733
- # @example Popping a member from a sorted set
1734
- # redis.bzpopmin('zset', 1)
1735
- # #=> ['zset', 'a', 1.0]
1736
- # @example Popping a member from multiple sorted sets
1737
- # redis.bzpopmin('zset1', 'zset2', 1)
1738
- # #=> ['zset1', 'a', 1.0]
1739
- #
1740
- # @params keys [Array<String>] one or multiple keys of the sorted sets
1741
- # @params timeout [Integer] the maximum number of seconds to block
1742
- #
1743
- # @return [Array<String, String, Float>] a touple of key, member and score
1744
- # @return [nil] when no element could be popped and the timeout expired
1745
- def bzpopmin(*args)
1746
- _bpop(:bzpopmin, args) do |reply|
1747
- reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
1748
- end
1749
- end
1750
-
1751
- # Get the score associated with the given member in a sorted set.
1752
- #
1753
- # @example Get the score for member "a"
1754
- # redis.zscore("zset", "a")
1755
- # # => 32.0
1756
- #
1757
- # @param [String] key
1758
- # @param [String] member
1759
- # @return [Float] score of the member
1760
- def zscore(key, member)
1761
- synchronize do |client|
1762
- client.call([:zscore, key, member], &Floatify)
1763
- end
1764
- end
1765
-
1766
- # Return a range of members in a sorted set, by index.
1767
- #
1768
- # @example Retrieve all members from a sorted set
1769
- # redis.zrange("zset", 0, -1)
1770
- # # => ["a", "b"]
1771
- # @example Retrieve all members and their scores from a sorted set
1772
- # redis.zrange("zset", 0, -1, :with_scores => true)
1773
- # # => [["a", 32.0], ["b", 64.0]]
1774
- #
1775
- # @param [String] key
1776
- # @param [Integer] start start index
1777
- # @param [Integer] stop stop index
1778
- # @param [Hash] options
1779
- # - `:with_scores => true`: include scores in output
1780
- #
1781
- # @return [Array<String>, Array<[String, Float]>]
1782
- # - when `:with_scores` is not specified, an array of members
1783
- # - when `:with_scores` is specified, an array with `[member, score]` pairs
1784
- def zrange(key, start, stop, withscores: false, with_scores: withscores)
1785
- args = [:zrange, key, start, stop]
1786
-
1787
- if with_scores
1788
- args << "WITHSCORES"
1789
- block = FloatifyPairs
1790
- end
1791
-
1792
- synchronize do |client|
1793
- client.call(args, &block)
1794
- end
1795
- end
1796
-
1797
- # Return a range of members in a sorted set, by index, with scores ordered
1798
- # from high to low.
1799
- #
1800
- # @example Retrieve all members from a sorted set
1801
- # redis.zrevrange("zset", 0, -1)
1802
- # # => ["b", "a"]
1803
- # @example Retrieve all members and their scores from a sorted set
1804
- # redis.zrevrange("zset", 0, -1, :with_scores => true)
1805
- # # => [["b", 64.0], ["a", 32.0]]
1806
- #
1807
- # @see #zrange
1808
- def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
1809
- args = [:zrevrange, key, start, stop]
1810
-
1811
- if with_scores
1812
- args << "WITHSCORES"
1813
- block = FloatifyPairs
1814
- end
1815
-
1816
- synchronize do |client|
1817
- client.call(args, &block)
1818
- end
1819
- end
1820
-
1821
- # Determine the index of a member in a sorted set.
1822
- #
1823
- # @param [String] key
1824
- # @param [String] member
1825
- # @return [Integer]
1826
- def zrank(key, member)
1827
- synchronize do |client|
1828
- client.call([:zrank, key, member])
1829
- end
1830
- end
1831
-
1832
- # Determine the index of a member in a sorted set, with scores ordered from
1833
- # high to low.
1834
- #
1835
- # @param [String] key
1836
- # @param [String] member
1837
- # @return [Integer]
1838
- def zrevrank(key, member)
1839
- synchronize do |client|
1840
- client.call([:zrevrank, key, member])
1841
- end
1842
- end
1843
-
1844
- # Remove all members in a sorted set within the given indexes.
1845
- #
1846
- # @example Remove first 5 members
1847
- # redis.zremrangebyrank("zset", 0, 4)
1848
- # # => 5
1849
- # @example Remove last 5 members
1850
- # redis.zremrangebyrank("zset", -5, -1)
1851
- # # => 5
1852
- #
1853
- # @param [String] key
1854
- # @param [Integer] start start index
1855
- # @param [Integer] stop stop index
1856
- # @return [Integer] number of members that were removed
1857
- def zremrangebyrank(key, start, stop)
1858
- synchronize do |client|
1859
- client.call([:zremrangebyrank, key, start, stop])
1860
- end
1861
- end
1862
-
1863
- # Count the members, with the same score in a sorted set, within the given lexicographical range.
1864
- #
1865
- # @example Count members matching a
1866
- # redis.zlexcount("zset", "[a", "[a\xff")
1867
- # # => 1
1868
- # @example Count members matching a-z
1869
- # redis.zlexcount("zset", "[a", "[z\xff")
1870
- # # => 26
1871
- #
1872
- # @param [String] key
1873
- # @param [String] min
1874
- # - inclusive minimum is specified by prefixing `(`
1875
- # - exclusive minimum is specified by prefixing `[`
1876
- # @param [String] max
1877
- # - inclusive maximum is specified by prefixing `(`
1878
- # - exclusive maximum is specified by prefixing `[`
1879
- #
1880
- # @return [Integer] number of members within the specified lexicographical range
1881
- def zlexcount(key, min, max)
1882
- synchronize do |client|
1883
- client.call([:zlexcount, key, min, max])
1884
- end
1885
- end
1886
-
1887
- # Return a range of members with the same score in a sorted set, by lexicographical ordering
1888
- #
1889
- # @example Retrieve members matching a
1890
- # redis.zrangebylex("zset", "[a", "[a\xff")
1891
- # # => ["aaren", "aarika", "abagael", "abby"]
1892
- # @example Retrieve the first 2 members matching a
1893
- # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
1894
- # # => ["aaren", "aarika"]
1895
- #
1896
- # @param [String] key
1897
- # @param [String] min
1898
- # - inclusive minimum is specified by prefixing `(`
1899
- # - exclusive minimum is specified by prefixing `[`
1900
- # @param [String] max
1901
- # - inclusive maximum is specified by prefixing `(`
1902
- # - exclusive maximum is specified by prefixing `[`
1903
- # @param [Hash] options
1904
- # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
1905
- # `count` members
1906
- #
1907
- # @return [Array<String>, Array<[String, Float]>]
1908
- def zrangebylex(key, min, max, limit: nil)
1909
- args = [:zrangebylex, key, min, max]
1910
-
1911
- if limit
1912
- args << "LIMIT"
1913
- args.concat(limit)
1914
- end
1915
-
1916
- synchronize do |client|
1917
- client.call(args)
1918
- end
1919
- end
1920
-
1921
- # Return a range of members with the same score in a sorted set, by reversed lexicographical ordering.
1922
- # Apart from the reversed ordering, #zrevrangebylex is similar to #zrangebylex.
1923
- #
1924
- # @example Retrieve members matching a
1925
- # redis.zrevrangebylex("zset", "[a", "[a\xff")
1926
- # # => ["abbygail", "abby", "abagael", "aaren"]
1927
- # @example Retrieve the last 2 members matching a
1928
- # redis.zrevrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
1929
- # # => ["abbygail", "abby"]
1930
- #
1931
- # @see #zrangebylex
1932
- def zrevrangebylex(key, max, min, limit: nil)
1933
- args = [:zrevrangebylex, key, max, min]
1934
-
1935
- if limit
1936
- args << "LIMIT"
1937
- args.concat(limit)
1938
- end
1939
-
1940
- synchronize do |client|
1941
- client.call(args)
1942
- end
1943
- end
1944
-
1945
- # Return a range of members in a sorted set, by score.
1946
- #
1947
- # @example Retrieve members with score `>= 5` and `< 100`
1948
- # redis.zrangebyscore("zset", "5", "(100")
1949
- # # => ["a", "b"]
1950
- # @example Retrieve the first 2 members with score `>= 0`
1951
- # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
1952
- # # => ["a", "b"]
1953
- # @example Retrieve members and their scores with scores `> 5`
1954
- # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
1955
- # # => [["a", 32.0], ["b", 64.0]]
1956
- #
1957
- # @param [String] key
1958
- # @param [String] min
1959
- # - inclusive minimum score is specified verbatim
1960
- # - exclusive minimum score is specified by prefixing `(`
1961
- # @param [String] max
1962
- # - inclusive maximum score is specified verbatim
1963
- # - exclusive maximum score is specified by prefixing `(`
1964
- # @param [Hash] options
1965
- # - `:with_scores => true`: include scores in output
1966
- # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
1967
- # `count` members
1968
- #
1969
- # @return [Array<String>, Array<[String, Float]>]
1970
- # - when `:with_scores` is not specified, an array of members
1971
- # - when `:with_scores` is specified, an array with `[member, score]` pairs
1972
- def zrangebyscore(key, min, max, withscores: false, with_scores: withscores, limit: nil)
1973
- args = [:zrangebyscore, key, min, max]
1974
-
1975
- if with_scores
1976
- args << "WITHSCORES"
1977
- block = FloatifyPairs
1978
- end
1979
-
1980
- if limit
1981
- args << "LIMIT"
1982
- args.concat(limit)
1983
- end
1984
-
1985
- synchronize do |client|
1986
- client.call(args, &block)
1987
- end
1988
- end
1989
-
1990
- # Return a range of members in a sorted set, by score, with scores ordered
1991
- # from high to low.
1992
- #
1993
- # @example Retrieve members with score `< 100` and `>= 5`
1994
- # redis.zrevrangebyscore("zset", "(100", "5")
1995
- # # => ["b", "a"]
1996
- # @example Retrieve the first 2 members with score `<= 0`
1997
- # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
1998
- # # => ["b", "a"]
1999
- # @example Retrieve members and their scores with scores `> 5`
2000
- # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
2001
- # # => [["b", 64.0], ["a", 32.0]]
2002
- #
2003
- # @see #zrangebyscore
2004
- def zrevrangebyscore(key, max, min, withscores: false, with_scores: withscores, limit: nil)
2005
- args = [:zrevrangebyscore, key, max, min]
2006
-
2007
- if with_scores
2008
- args << "WITHSCORES"
2009
- block = FloatifyPairs
2010
- end
2011
-
2012
- if limit
2013
- args << "LIMIT"
2014
- args.concat(limit)
2015
- end
2016
-
2017
- synchronize do |client|
2018
- client.call(args, &block)
2019
- end
2020
- end
2021
-
2022
- # Remove all members in a sorted set within the given scores.
2023
- #
2024
- # @example Remove members with score `>= 5` and `< 100`
2025
- # redis.zremrangebyscore("zset", "5", "(100")
2026
- # # => 2
2027
- # @example Remove members with scores `> 5`
2028
- # redis.zremrangebyscore("zset", "(5", "+inf")
2029
- # # => 2
2030
- #
2031
- # @param [String] key
2032
- # @param [String] min
2033
- # - inclusive minimum score is specified verbatim
2034
- # - exclusive minimum score is specified by prefixing `(`
2035
- # @param [String] max
2036
- # - inclusive maximum score is specified verbatim
2037
- # - exclusive maximum score is specified by prefixing `(`
2038
- # @return [Integer] number of members that were removed
2039
- def zremrangebyscore(key, min, max)
2040
- synchronize do |client|
2041
- client.call([:zremrangebyscore, key, min, max])
2042
- end
2043
- end
2044
-
2045
- # Count the members in a sorted set with scores within the given values.
2046
- #
2047
- # @example Count members with score `>= 5` and `< 100`
2048
- # redis.zcount("zset", "5", "(100")
2049
- # # => 2
2050
- # @example Count members with scores `> 5`
2051
- # redis.zcount("zset", "(5", "+inf")
2052
- # # => 2
2053
- #
2054
- # @param [String] key
2055
- # @param [String] min
2056
- # - inclusive minimum score is specified verbatim
2057
- # - exclusive minimum score is specified by prefixing `(`
2058
- # @param [String] max
2059
- # - inclusive maximum score is specified verbatim
2060
- # - exclusive maximum score is specified by prefixing `(`
2061
- # @return [Integer] number of members in within the specified range
2062
- def zcount(key, min, max)
2063
- synchronize do |client|
2064
- client.call([:zcount, key, min, max])
2065
- end
2066
- end
2067
-
2068
- # Return the intersection of multiple sorted sets
2069
- #
2070
- # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`
2071
- # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0])
2072
- # # => ["v1", "v2"]
2073
- # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`, and their scores
2074
- # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0], :with_scores => true)
2075
- # # => [["v1", 3.0], ["v2", 6.0]]
2076
- #
2077
- # @param [String, Array<String>] keys one or more keys to intersect
2078
- # @param [Hash] options
2079
- # - `:weights => [Float, Float, ...]`: weights to associate with source
2080
- # sorted sets
2081
- # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
2082
- # - `:with_scores => true`: include scores in output
2083
- #
2084
- # @return [Array<String>, Array<[String, Float]>]
2085
- # - when `:with_scores` is not specified, an array of members
2086
- # - when `:with_scores` is specified, an array with `[member, score]` pairs
2087
- def zinter(*keys, weights: nil, aggregate: nil, with_scores: false)
2088
- args = [:zinter, keys.size, *keys]
2089
-
2090
- if weights
2091
- args << "WEIGHTS"
2092
- args.concat(weights)
2093
- end
2094
-
2095
- args << "AGGREGATE" << aggregate if aggregate
2096
-
2097
- if with_scores
2098
- args << "WITHSCORES"
2099
- block = FloatifyPairs
2100
- end
2101
-
2102
- synchronize do |client|
2103
- client.call(args, &block)
2104
- end
2105
- end
2106
-
2107
- # Intersect multiple sorted sets and store the resulting sorted set in a new
2108
- # key.
2109
- #
2110
- # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
2111
- # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
2112
- # # => 4
2113
- #
2114
- # @param [String] destination destination key
2115
- # @param [Array<String>] keys source keys
2116
- # @param [Hash] options
2117
- # - `:weights => [Float, Float, ...]`: weights to associate with source
2118
- # sorted sets
2119
- # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
2120
- # @return [Integer] number of elements in the resulting sorted set
2121
- def zinterstore(destination, keys, weights: nil, aggregate: nil)
2122
- args = [:zinterstore, destination, keys.size, *keys]
2123
-
2124
- if weights
2125
- args << "WEIGHTS"
2126
- args.concat(weights)
2127
- end
2128
-
2129
- args << "AGGREGATE" << aggregate if aggregate
2130
-
2131
- synchronize do |client|
2132
- client.call(args)
2133
- end
2134
- end
2135
-
2136
- # Add multiple sorted sets and store the resulting sorted set in a new key.
2137
- #
2138
- # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
2139
- # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
2140
- # # => 8
2141
- #
2142
- # @param [String] destination destination key
2143
- # @param [Array<String>] keys source keys
2144
- # @param [Hash] options
2145
- # - `:weights => [Float, Float, ...]`: weights to associate with source
2146
- # sorted sets
2147
- # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
2148
- # @return [Integer] number of elements in the resulting sorted set
2149
- def zunionstore(destination, keys, weights: nil, aggregate: nil)
2150
- args = [:zunionstore, destination, keys.size, *keys]
2151
-
2152
- if weights
2153
- args << "WEIGHTS"
2154
- args.concat(weights)
2155
- end
2156
-
2157
- args << "AGGREGATE" << aggregate if aggregate
2158
-
2159
- synchronize do |client|
2160
- client.call(args)
2161
- end
2162
- end
2163
-
2164
- # Get the number of fields in a hash.
2165
- #
2166
- # @param [String] key
2167
- # @return [Integer] number of fields in the hash
2168
- def hlen(key)
2169
- synchronize do |client|
2170
- client.call([:hlen, key])
2171
- end
2172
- end
2173
-
2174
- # Set one or more hash values.
2175
- #
2176
- # @example
2177
- # redis.hset("hash", "f1", "v1", "f2", "v2") # => 2
2178
- # redis.hset("hash", { "f1" => "v1", "f2" => "v2" }) # => 2
2179
- #
2180
- # @param [String] key
2181
- # @param [Array<String> | Hash<String, String>] attrs array or hash of fields and values
2182
- # @return [Integer] The number of fields that were added to the hash
2183
- def hset(key, *attrs)
2184
- attrs = attrs.first.flatten if attrs.size == 1 && attrs.first.is_a?(Hash)
2185
-
2186
- synchronize do |client|
2187
- client.call([:hset, key, *attrs])
2188
- end
2189
- end
2190
-
2191
- # Set the value of a hash field, only if the field does not exist.
2192
- #
2193
- # @param [String] key
2194
- # @param [String] field
2195
- # @param [String] value
2196
- # @return [Boolean] whether or not the field was **added** to the hash
2197
- def hsetnx(key, field, value)
2198
- synchronize do |client|
2199
- client.call([:hsetnx, key, field, value], &Boolify)
2200
- end
2201
- end
2202
-
2203
- # Set one or more hash values.
2204
- #
2205
- # @example
2206
- # redis.hmset("hash", "f1", "v1", "f2", "v2")
2207
- # # => "OK"
2208
- #
2209
- # @param [String] key
2210
- # @param [Array<String>] attrs array of fields and values
2211
- # @return [String] `"OK"`
2212
- #
2213
- # @see #mapped_hmset
2214
- def hmset(key, *attrs)
2215
- synchronize do |client|
2216
- client.call([:hmset, key] + attrs)
2217
- end
2218
- end
2219
-
2220
- # Set one or more hash values.
2221
- #
2222
- # @example
2223
- # redis.mapped_hmset("hash", { "f1" => "v1", "f2" => "v2" })
2224
- # # => "OK"
2225
- #
2226
- # @param [String] key
2227
- # @param [Hash] hash a non-empty hash with fields mapping to values
2228
- # @return [String] `"OK"`
2229
- #
2230
- # @see #hmset
2231
- def mapped_hmset(key, hash)
2232
- hmset(key, hash.to_a.flatten)
2233
- end
2234
-
2235
- # Get the value of a hash field.
2236
- #
2237
- # @param [String] key
2238
- # @param [String] field
2239
- # @return [String]
2240
- def hget(key, field)
2241
- synchronize do |client|
2242
- client.call([:hget, key, field])
2243
- end
2244
- end
2245
-
2246
- # Get the values of all the given hash fields.
2247
- #
2248
- # @example
2249
- # redis.hmget("hash", "f1", "f2")
2250
- # # => ["v1", "v2"]
2251
- #
2252
- # @param [String] key
2253
- # @param [Array<String>] fields array of fields
2254
- # @return [Array<String>] an array of values for the specified fields
2255
- #
2256
- # @see #mapped_hmget
2257
- def hmget(key, *fields, &blk)
2258
- synchronize do |client|
2259
- client.call([:hmget, key] + fields, &blk)
2260
- end
2261
- end
2262
-
2263
- # Get the values of all the given hash fields.
2264
- #
2265
- # @example
2266
- # redis.mapped_hmget("hash", "f1", "f2")
2267
- # # => { "f1" => "v1", "f2" => "v2" }
2268
- #
2269
- # @param [String] key
2270
- # @param [Array<String>] fields array of fields
2271
- # @return [Hash] a hash mapping the specified fields to their values
2272
- #
2273
- # @see #hmget
2274
- def mapped_hmget(key, *fields)
2275
- hmget(key, *fields) do |reply|
2276
- if reply.is_a?(Array)
2277
- Hash[fields.zip(reply)]
2278
- else
2279
- reply
2280
- end
2281
- end
2282
- end
2283
-
2284
- # Delete one or more hash fields.
2285
- #
2286
- # @param [String] key
2287
- # @param [String, Array<String>] field
2288
- # @return [Integer] the number of fields that were removed from the hash
2289
- def hdel(key, *fields)
2290
- synchronize do |client|
2291
- client.call([:hdel, key, *fields])
2292
- end
2293
- end
2294
-
2295
- # Determine if a hash field exists.
2296
- #
2297
- # @param [String] key
2298
- # @param [String] field
2299
- # @return [Boolean] whether or not the field exists in the hash
2300
- def hexists(key, field)
2301
- synchronize do |client|
2302
- client.call([:hexists, key, field], &Boolify)
2303
- end
2304
- end
2305
-
2306
- # Increment the integer value of a hash field by the given integer number.
2307
- #
2308
- # @param [String] key
2309
- # @param [String] field
2310
- # @param [Integer] increment
2311
- # @return [Integer] value of the field after incrementing it
2312
- def hincrby(key, field, increment)
2313
- synchronize do |client|
2314
- client.call([:hincrby, key, field, increment])
2315
- end
2316
- end
2317
-
2318
- # Increment the numeric value of a hash field by the given float number.
2319
- #
2320
- # @param [String] key
2321
- # @param [String] field
2322
- # @param [Float] increment
2323
- # @return [Float] value of the field after incrementing it
2324
- def hincrbyfloat(key, field, increment)
2325
- synchronize do |client|
2326
- client.call([:hincrbyfloat, key, field, increment], &Floatify)
2327
- end
2328
- end
2329
-
2330
- # Get all the fields in a hash.
2331
- #
2332
- # @param [String] key
2333
- # @return [Array<String>]
2334
- def hkeys(key)
2335
- synchronize do |client|
2336
- client.call([:hkeys, key])
2337
- end
2338
- end
2339
-
2340
- # Get all the values in a hash.
2341
- #
2342
- # @param [String] key
2343
- # @return [Array<String>]
2344
- def hvals(key)
2345
- synchronize do |client|
2346
- client.call([:hvals, key])
2347
- end
2348
- end
2349
-
2350
- # Get all the fields and values in a hash.
2351
- #
2352
- # @param [String] key
2353
- # @return [Hash<String, String>]
2354
- def hgetall(key)
2355
- synchronize do |client|
2356
- client.call([:hgetall, key], &Hashify)
2357
- end
2358
- end
2359
-
2360
- # Post a message to a channel.
2361
- def publish(channel, message)
2362
- synchronize do |client|
2363
- client.call([:publish, channel, message])
2364
- end
2365
- end
2366
-
2367
- def subscribed?
2368
- synchronize do |client|
2369
- client.is_a? SubscribedClient
2370
- end
2371
- end
2372
-
2373
- # Listen for messages published to the given channels.
2374
- def subscribe(*channels, &block)
2375
- synchronize do |_client|
2376
- _subscription(:subscribe, 0, channels, block)
2377
- end
2378
- end
2379
-
2380
- # Listen for messages published to the given channels. Throw a timeout error
2381
- # if there is no messages for a timeout period.
2382
- def subscribe_with_timeout(timeout, *channels, &block)
2383
- synchronize do |_client|
2384
- _subscription(:subscribe_with_timeout, timeout, channels, block)
2385
- end
2386
- end
2387
-
2388
- # Stop listening for messages posted to the given channels.
2389
- def unsubscribe(*channels)
2390
- synchronize do |client|
2391
- raise "Can't unsubscribe if not subscribed." unless subscribed?
2392
-
2393
- client.unsubscribe(*channels)
2394
- end
2395
- end
2396
-
2397
- # Listen for messages published to channels matching the given patterns.
2398
- def psubscribe(*channels, &block)
2399
- synchronize do |_client|
2400
- _subscription(:psubscribe, 0, channels, block)
2401
- end
2402
- end
2403
-
2404
- # Listen for messages published to channels matching the given patterns.
2405
- # Throw a timeout error if there is no messages for a timeout period.
2406
- def psubscribe_with_timeout(timeout, *channels, &block)
2407
- synchronize do |_client|
2408
- _subscription(:psubscribe_with_timeout, timeout, channels, block)
2409
- end
2410
- end
2411
-
2412
- # Stop listening for messages posted to channels matching the given patterns.
2413
- def punsubscribe(*channels)
2414
- synchronize do |client|
2415
- raise "Can't unsubscribe if not subscribed." unless subscribed?
2416
-
2417
- client.punsubscribe(*channels)
2418
- end
2419
- end
2420
-
2421
- # Inspect the state of the Pub/Sub subsystem.
2422
- # Possible subcommands: channels, numsub, numpat.
2423
- def pubsub(subcommand, *args)
2424
- synchronize do |client|
2425
- client.call([:pubsub, subcommand] + args)
2426
- end
2427
- end
2428
-
2429
- # Watch the given keys to determine execution of the MULTI/EXEC block.
2430
- #
2431
- # Using a block is optional, but is necessary for thread-safety.
2432
- #
2433
- # An `#unwatch` is automatically issued if an exception is raised within the
2434
- # block that is a subclass of StandardError and is not a ConnectionError.
2435
- #
2436
- # @example With a block
2437
- # redis.watch("key") do
2438
- # if redis.get("key") == "some value"
2439
- # redis.multi do |multi|
2440
- # multi.set("key", "other value")
2441
- # multi.incr("counter")
2442
- # end
2443
- # else
2444
- # redis.unwatch
2445
- # end
2446
- # end
2447
- # # => ["OK", 6]
2448
- #
2449
- # @example Without a block
2450
- # redis.watch("key")
2451
- # # => "OK"
2452
- #
2453
- # @param [String, Array<String>] keys one or more keys to watch
2454
- # @return [Object] if using a block, returns the return value of the block
2455
- # @return [String] if not using a block, returns `OK`
2456
- #
2457
- # @see #unwatch
2458
- # @see #multi
2459
- def watch(*keys)
2460
- synchronize do |client|
2461
- res = client.call([:watch, *keys])
2462
-
2463
- if block_given?
2464
- begin
2465
- yield(self)
2466
- rescue ConnectionError
2467
- raise
2468
- rescue StandardError
2469
- unwatch
2470
- raise
2471
- end
2472
- else
2473
- res
2474
- end
2475
- end
2476
- end
2477
-
2478
- # Forget about all watched keys.
2479
- #
2480
- # @return [String] `OK`
2481
- #
2482
- # @see #watch
2483
- # @see #multi
2484
- def unwatch
2485
- synchronize do |client|
2486
- client.call([:unwatch])
2487
- end
2488
- end
2489
-
2490
- def pipelined
2491
- synchronize do |prior_client|
2492
- begin
2493
- @client = Pipeline.new(prior_client)
2494
- yield(self)
2495
- prior_client.call_pipeline(@client)
2496
- ensure
2497
- @client = prior_client
2498
- end
2499
- end
2500
- end
2501
-
2502
- # Mark the start of a transaction block.
2503
- #
2504
- # Passing a block is optional.
2505
- #
2506
- # @example With a block
2507
- # redis.multi do |multi|
2508
- # multi.set("key", "value")
2509
- # multi.incr("counter")
2510
- # end # => ["OK", 6]
2511
- #
2512
- # @example Without a block
2513
- # redis.multi
2514
- # # => "OK"
2515
- # redis.set("key", "value")
2516
- # # => "QUEUED"
2517
- # redis.incr("counter")
2518
- # # => "QUEUED"
2519
- # redis.exec
2520
- # # => ["OK", 6]
2521
- #
2522
- # @yield [multi] the commands that are called inside this block are cached
2523
- # and written to the server upon returning from it
2524
- # @yieldparam [Redis] multi `self`
2525
- #
2526
- # @return [String, Array<...>]
2527
- # - when a block is not given, `OK`
2528
- # - when a block is given, an array with replies
2529
- #
2530
- # @see #watch
2531
- # @see #unwatch
2532
- def multi
2533
- synchronize do |prior_client|
2534
- if !block_given?
2535
- prior_client.call([:multi])
2536
- else
2537
- begin
2538
- @client = Pipeline::Multi.new(prior_client)
2539
- yield(self)
2540
- prior_client.call_pipeline(@client)
2541
- ensure
2542
- @client = prior_client
2543
- end
2544
- end
2545
- end
2546
- end
2547
-
2548
- # Execute all commands issued after MULTI.
2549
- #
2550
- # Only call this method when `#multi` was called **without** a block.
2551
- #
2552
- # @return [nil, Array<...>]
2553
- # - when commands were not executed, `nil`
2554
- # - when commands were executed, an array with their replies
2555
- #
2556
- # @see #multi
2557
- # @see #discard
2558
- def exec
2559
- synchronize do |client|
2560
- client.call([:exec])
2561
- end
2562
- end
2563
-
2564
- # Discard all commands issued after MULTI.
2565
- #
2566
- # Only call this method when `#multi` was called **without** a block.
2567
- #
2568
- # @return [String] `"OK"`
2569
- #
2570
- # @see #multi
2571
- # @see #exec
2572
- def discard
2573
- synchronize do |client|
2574
- client.call([:discard])
2575
- end
2576
- end
2577
-
2578
- # Control remote script registry.
2579
- #
2580
- # @example Load a script
2581
- # sha = redis.script(:load, "return 1")
2582
- # # => <sha of this script>
2583
- # @example Check if a script exists
2584
- # redis.script(:exists, sha)
2585
- # # => true
2586
- # @example Check if multiple scripts exist
2587
- # redis.script(:exists, [sha, other_sha])
2588
- # # => [true, false]
2589
- # @example Flush the script registry
2590
- # redis.script(:flush)
2591
- # # => "OK"
2592
- # @example Kill a running script
2593
- # redis.script(:kill)
2594
- # # => "OK"
2595
- #
2596
- # @param [String] subcommand e.g. `exists`, `flush`, `load`, `kill`
2597
- # @param [Array<String>] args depends on subcommand
2598
- # @return [String, Boolean, Array<Boolean>, ...] depends on subcommand
2599
- #
2600
- # @see #eval
2601
- # @see #evalsha
2602
- def script(subcommand, *args)
2603
- subcommand = subcommand.to_s.downcase
2604
-
2605
- if subcommand == "exists"
2606
- synchronize do |client|
2607
- arg = args.first
2608
-
2609
- client.call([:script, :exists, arg]) do |reply|
2610
- reply = reply.map { |r| Boolify.call(r) }
2611
-
2612
- if arg.is_a?(Array)
2613
- reply
2614
- else
2615
- reply.first
2616
- end
2617
- end
2618
- end
2619
- else
2620
- synchronize do |client|
2621
- client.call([:script, subcommand] + args)
2622
- end
2623
- end
2624
- end
2625
-
2626
- def _eval(cmd, args)
2627
- script = args.shift
2628
- options = args.pop if args.last.is_a?(Hash)
2629
- options ||= {}
2630
-
2631
- keys = args.shift || options[:keys] || []
2632
- argv = args.shift || options[:argv] || []
2633
-
2634
- synchronize do |client|
2635
- client.call([cmd, script, keys.length] + keys + argv)
2636
- end
2637
- end
2638
-
2639
- # Evaluate Lua script.
2640
- #
2641
- # @example EVAL without KEYS nor ARGV
2642
- # redis.eval("return 1")
2643
- # # => 1
2644
- # @example EVAL with KEYS and ARGV as array arguments
2645
- # redis.eval("return { KEYS, ARGV }", ["k1", "k2"], ["a1", "a2"])
2646
- # # => [["k1", "k2"], ["a1", "a2"]]
2647
- # @example EVAL with KEYS and ARGV in a hash argument
2648
- # redis.eval("return { KEYS, ARGV }", :keys => ["k1", "k2"], :argv => ["a1", "a2"])
2649
- # # => [["k1", "k2"], ["a1", "a2"]]
2650
- #
2651
- # @param [Array<String>] keys optional array with keys to pass to the script
2652
- # @param [Array<String>] argv optional array with arguments to pass to the script
2653
- # @param [Hash] options
2654
- # - `:keys => Array<String>`: optional array with keys to pass to the script
2655
- # - `:argv => Array<String>`: optional array with arguments to pass to the script
2656
- # @return depends on the script
2657
- #
2658
- # @see #script
2659
- # @see #evalsha
2660
- def eval(*args)
2661
- _eval(:eval, args)
2662
- end
2663
-
2664
- # Evaluate Lua script by its SHA.
2665
- #
2666
- # @example EVALSHA without KEYS nor ARGV
2667
- # redis.evalsha(sha)
2668
- # # => <depends on script>
2669
- # @example EVALSHA with KEYS and ARGV as array arguments
2670
- # redis.evalsha(sha, ["k1", "k2"], ["a1", "a2"])
2671
- # # => <depends on script>
2672
- # @example EVALSHA with KEYS and ARGV in a hash argument
2673
- # redis.evalsha(sha, :keys => ["k1", "k2"], :argv => ["a1", "a2"])
2674
- # # => <depends on script>
2675
- #
2676
- # @param [Array<String>] keys optional array with keys to pass to the script
2677
- # @param [Array<String>] argv optional array with arguments to pass to the script
2678
- # @param [Hash] options
2679
- # - `:keys => Array<String>`: optional array with keys to pass to the script
2680
- # - `:argv => Array<String>`: optional array with arguments to pass to the script
2681
- # @return depends on the script
2682
- #
2683
- # @see #script
2684
- # @see #eval
2685
- def evalsha(*args)
2686
- _eval(:evalsha, args)
2687
- end
2688
-
2689
- def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
2690
- # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
2691
-
2692
- args << cursor
2693
- args << "MATCH" << match if match
2694
- args << "COUNT" << count if count
2695
- args << "TYPE" << type if type
2696
-
2697
- synchronize do |client|
2698
- client.call([command] + args, &block)
2699
- end
2700
- end
2701
-
2702
- # Scan the keyspace
2703
- #
2704
- # @example Retrieve the first batch of keys
2705
- # redis.scan(0)
2706
- # # => ["4", ["key:21", "key:47", "key:42"]]
2707
- # @example Retrieve a batch of keys matching a pattern
2708
- # redis.scan(4, :match => "key:1?")
2709
- # # => ["92", ["key:13", "key:18"]]
2710
- # @example Retrieve a batch of keys of a certain type
2711
- # redis.scan(92, :type => "zset")
2712
- # # => ["173", ["sortedset:14", "sortedset:78"]]
2713
- #
2714
- # @param [String, Integer] cursor the cursor of the iteration
2715
- # @param [Hash] options
2716
- # - `:match => String`: only return keys matching the pattern
2717
- # - `:count => Integer`: return count keys at most per iteration
2718
- # - `:type => String`: return keys only of the given type
2719
- #
2720
- # @return [String, Array<String>] the next cursor and all found keys
2721
- def scan(cursor, **options)
2722
- _scan(:scan, cursor, [], **options)
2723
- end
2724
-
2725
- # Scan the keyspace
2726
- #
2727
- # @example Retrieve all of the keys (with possible duplicates)
2728
- # redis.scan_each.to_a
2729
- # # => ["key:21", "key:47", "key:42"]
2730
- # @example Execute block for each key matching a pattern
2731
- # redis.scan_each(:match => "key:1?") {|key| puts key}
2732
- # # => key:13
2733
- # # => key:18
2734
- # @example Execute block for each key of a type
2735
- # redis.scan_each(:type => "hash") {|key| puts redis.type(key)}
2736
- # # => "hash"
2737
- # # => "hash"
2738
- #
2739
- # @param [Hash] options
2740
- # - `:match => String`: only return keys matching the pattern
2741
- # - `:count => Integer`: return count keys at most per iteration
2742
- # - `:type => String`: return keys only of the given type
2743
- #
2744
- # @return [Enumerator] an enumerator for all found keys
2745
- def scan_each(**options, &block)
2746
- return to_enum(:scan_each, **options) unless block_given?
2747
-
2748
- cursor = 0
2749
- loop do
2750
- cursor, keys = scan(cursor, **options)
2751
- keys.each(&block)
2752
- break if cursor == "0"
2753
- end
2754
- end
2755
-
2756
- # Scan a hash
2757
- #
2758
- # @example Retrieve the first batch of key/value pairs in a hash
2759
- # redis.hscan("hash", 0)
2760
- #
2761
- # @param [String, Integer] cursor the cursor of the iteration
2762
- # @param [Hash] options
2763
- # - `:match => String`: only return keys matching the pattern
2764
- # - `:count => Integer`: return count keys at most per iteration
2765
- #
2766
- # @return [String, Array<[String, String]>] the next cursor and all found keys
2767
- def hscan(key, cursor, **options)
2768
- _scan(:hscan, cursor, [key], **options) do |reply|
2769
- [reply[0], reply[1].each_slice(2).to_a]
2770
- end
2771
- end
2772
-
2773
- # Scan a hash
2774
- #
2775
- # @example Retrieve all of the key/value pairs in a hash
2776
- # redis.hscan_each("hash").to_a
2777
- # # => [["key70", "70"], ["key80", "80"]]
2778
- #
2779
- # @param [Hash] options
2780
- # - `:match => String`: only return keys matching the pattern
2781
- # - `:count => Integer`: return count keys at most per iteration
2782
- #
2783
- # @return [Enumerator] an enumerator for all found keys
2784
- def hscan_each(key, **options, &block)
2785
- return to_enum(:hscan_each, key, **options) unless block_given?
2786
-
2787
- cursor = 0
2788
- loop do
2789
- cursor, values = hscan(key, cursor, **options)
2790
- values.each(&block)
2791
- break if cursor == "0"
2792
- end
2793
- end
2794
-
2795
- # Scan a sorted set
2796
- #
2797
- # @example Retrieve the first batch of key/value pairs in a hash
2798
- # redis.zscan("zset", 0)
2799
- #
2800
- # @param [String, Integer] cursor the cursor of the iteration
2801
- # @param [Hash] options
2802
- # - `:match => String`: only return keys matching the pattern
2803
- # - `:count => Integer`: return count keys at most per iteration
2804
- #
2805
- # @return [String, Array<[String, Float]>] the next cursor and all found
2806
- # members and scores
2807
- def zscan(key, cursor, **options)
2808
- _scan(:zscan, cursor, [key], **options) do |reply|
2809
- [reply[0], FloatifyPairs.call(reply[1])]
2810
- end
2811
- end
2812
-
2813
- # Scan a sorted set
2814
- #
2815
- # @example Retrieve all of the members/scores in a sorted set
2816
- # redis.zscan_each("zset").to_a
2817
- # # => [["key70", "70"], ["key80", "80"]]
2818
- #
2819
- # @param [Hash] options
2820
- # - `:match => String`: only return keys matching the pattern
2821
- # - `:count => Integer`: return count keys at most per iteration
2822
- #
2823
- # @return [Enumerator] an enumerator for all found scores and members
2824
- def zscan_each(key, **options, &block)
2825
- return to_enum(:zscan_each, key, **options) unless block_given?
2826
-
2827
- cursor = 0
2828
- loop do
2829
- cursor, values = zscan(key, cursor, **options)
2830
- values.each(&block)
2831
- break if cursor == "0"
2832
- end
2833
- end
2834
-
2835
- # Scan a set
2836
- #
2837
- # @example Retrieve the first batch of keys in a set
2838
- # redis.sscan("set", 0)
2839
- #
2840
- # @param [String, Integer] cursor the cursor of the iteration
2841
- # @param [Hash] options
2842
- # - `:match => String`: only return keys matching the pattern
2843
- # - `:count => Integer`: return count keys at most per iteration
2844
- #
2845
- # @return [String, Array<String>] the next cursor and all found members
2846
- def sscan(key, cursor, **options)
2847
- _scan(:sscan, cursor, [key], **options)
2848
- end
2849
-
2850
- # Scan a set
2851
- #
2852
- # @example Retrieve all of the keys in a set
2853
- # redis.sscan_each("set").to_a
2854
- # # => ["key1", "key2", "key3"]
2855
- #
2856
- # @param [Hash] options
2857
- # - `:match => String`: only return keys matching the pattern
2858
- # - `:count => Integer`: return count keys at most per iteration
2859
- #
2860
- # @return [Enumerator] an enumerator for all keys in the set
2861
- def sscan_each(key, **options, &block)
2862
- return to_enum(:sscan_each, key, **options) unless block_given?
2863
-
2864
- cursor = 0
2865
- loop do
2866
- cursor, keys = sscan(key, cursor, **options)
2867
- keys.each(&block)
2868
- break if cursor == "0"
2869
- end
2870
- end
2871
-
2872
- # Add one or more members to a HyperLogLog structure.
2873
- #
2874
- # @param [String] key
2875
- # @param [String, Array<String>] member one member, or array of members
2876
- # @return [Boolean] true if at least 1 HyperLogLog internal register was altered. false otherwise.
2877
- def pfadd(key, member)
2878
- synchronize do |client|
2879
- client.call([:pfadd, key, member], &Boolify)
2880
- end
2881
- end
2882
-
2883
- # Get the approximate cardinality of members added to HyperLogLog structure.
2884
- #
2885
- # If called with multiple keys, returns the approximate cardinality of the
2886
- # union of the HyperLogLogs contained in the keys.
2887
- #
2888
- # @param [String, Array<String>] keys
2889
- # @return [Integer]
2890
- def pfcount(*keys)
2891
- synchronize do |client|
2892
- client.call([:pfcount] + keys)
2893
- end
2894
- end
2895
-
2896
- # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
2897
- # the observed Sets of the source HyperLogLog structures.
2898
- #
2899
- # @param [String] dest_key destination key
2900
- # @param [String, Array<String>] source_key source key, or array of keys
2901
- # @return [Boolean]
2902
- def pfmerge(dest_key, *source_key)
2903
- synchronize do |client|
2904
- client.call([:pfmerge, dest_key, *source_key], &BoolifySet)
2905
- end
2906
- end
2907
-
2908
- # Adds the specified geospatial items (latitude, longitude, name) to the specified key
2909
- #
2910
- # @param [String] key
2911
- # @param [Array] member arguemnts for member or members: longitude, latitude, name
2912
- # @return [Integer] number of elements added to the sorted set
2913
- def geoadd(key, *member)
2914
- synchronize do |client|
2915
- client.call([:geoadd, key, *member])
2916
- end
2917
- end
2918
-
2919
- # Returns geohash string representing position for specified members of the specified key.
2920
- #
2921
- # @param [String] key
2922
- # @param [String, Array<String>] member one member or array of members
2923
- # @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
2924
- def geohash(key, member)
2925
- synchronize do |client|
2926
- client.call([:geohash, key, member])
2927
- end
2928
- end
2929
-
2930
- # Query a sorted set representing a geospatial index to fetch members matching a
2931
- # given maximum distance from a point
2932
- #
2933
- # @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
2934
- # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest
2935
- # or the farthest to the nearest relative to the center
2936
- # @param [Integer] count limit the results to the first N matching items
2937
- # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2938
- # @return [Array<String>] may be changed with `options`
2939
-
2940
- def georadius(*args, **geoptions)
2941
- geoarguments = _geoarguments(*args, **geoptions)
2942
-
2943
- synchronize do |client|
2944
- client.call([:georadius, *geoarguments])
2945
- end
2946
- end
2947
-
2948
- # Query a sorted set representing a geospatial index to fetch members matching a
2949
- # given maximum distance from an already existing member
2950
- #
2951
- # @param [Array] args key, member, radius, unit(m|km|ft|mi)
2952
- # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest
2953
- # to the nearest relative to the center
2954
- # @param [Integer] count limit the results to the first N matching items
2955
- # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2956
- # @return [Array<String>] may be changed with `options`
2957
-
2958
- def georadiusbymember(*args, **geoptions)
2959
- geoarguments = _geoarguments(*args, **geoptions)
2960
-
2961
- synchronize do |client|
2962
- client.call([:georadiusbymember, *geoarguments])
2963
- end
2964
- end
2965
-
2966
- # Returns longitude and latitude of members of a geospatial index
2967
- #
2968
- # @param [String] key
2969
- # @param [String, Array<String>] member one member or array of members
2970
- # @return [Array<Array<String>, nil>] returns array of elements, where each
2971
- # element is either array of longitude and latitude or nil
2972
- def geopos(key, member)
2973
- synchronize do |client|
2974
- client.call([:geopos, key, member])
2975
- end
2976
- end
2977
-
2978
- # Returns the distance between two members of a geospatial index
2979
- #
2980
- # @param [String ]key
2981
- # @param [Array<String>] members
2982
- # @param ['m', 'km', 'mi', 'ft'] unit
2983
- # @return [String, nil] returns distance in spefied unit if both members present, nil otherwise.
2984
- def geodist(key, member1, member2, unit = 'm')
2985
- synchronize do |client|
2986
- client.call([:geodist, key, member1, member2, unit])
2987
- end
2988
- end
2989
-
2990
- # Returns the stream information each subcommand.
2991
- #
2992
- # @example stream
2993
- # redis.xinfo(:stream, 'mystream')
2994
- # @example groups
2995
- # redis.xinfo(:groups, 'mystream')
2996
- # @example consumers
2997
- # redis.xinfo(:consumers, 'mystream', 'mygroup')
2998
- #
2999
- # @param subcommand [String] e.g. `stream` `groups` `consumers`
3000
- # @param key [String] the stream key
3001
- # @param group [String] the consumer group name, required if subcommand is `consumers`
3002
- #
3003
- # @return [Hash] information of the stream if subcommand is `stream`
3004
- # @return [Array<Hash>] information of the consumer groups if subcommand is `groups`
3005
- # @return [Array<Hash>] information of the consumers if subcommand is `consumers`
3006
- def xinfo(subcommand, key, group = nil)
3007
- args = [:xinfo, subcommand, key, group].compact
3008
- synchronize do |client|
3009
- client.call(args) do |reply|
3010
- case subcommand.to_s.downcase
3011
- when 'stream' then Hashify.call(reply)
3012
- when 'groups', 'consumers' then reply.map { |arr| Hashify.call(arr) }
3013
- else reply
3014
- end
3015
- end
3016
- end
3017
- end
3018
-
3019
- # Add new entry to the stream.
3020
- #
3021
- # @example Without options
3022
- # redis.xadd('mystream', f1: 'v1', f2: 'v2')
3023
- # @example With options
3024
- # redis.xadd('mystream', { f1: 'v1', f2: 'v2' }, id: '0-0', maxlen: 1000, approximate: true)
3025
- #
3026
- # @param key [String] the stream key
3027
- # @param entry [Hash] one or multiple field-value pairs
3028
- # @param opts [Hash] several options for `XADD` command
3029
- #
3030
- # @option opts [String] :id the entry id, default value is `*`, it means auto generation
3031
- # @option opts [Integer] :maxlen max length of entries
3032
- # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
3033
- #
3034
- # @return [String] the entry id
3035
- def xadd(key, entry, approximate: nil, maxlen: nil, id: '*')
3036
- args = [:xadd, key]
3037
- if maxlen
3038
- args << "MAXLEN"
3039
- args << "~" if approximate
3040
- args << maxlen
3041
- end
3042
- args << id
3043
- args.concat(entry.to_a.flatten)
3044
- synchronize { |client| client.call(args) }
3045
- end
3046
-
3047
- # Trims older entries of the stream if needed.
3048
- #
3049
- # @example Without options
3050
- # redis.xtrim('mystream', 1000)
3051
- # @example With options
3052
- # redis.xtrim('mystream', 1000, approximate: true)
3053
- #
3054
- # @param key [String] the stream key
3055
- # @param mexlen [Integer] max length of entries
3056
- # @param approximate [Boolean] whether to add `~` modifier of maxlen or not
3057
- #
3058
- # @return [Integer] the number of entries actually deleted
3059
- def xtrim(key, maxlen, approximate: false)
3060
- args = [:xtrim, key, 'MAXLEN', (approximate ? '~' : nil), maxlen].compact
3061
- synchronize { |client| client.call(args) }
3062
- end
3063
-
3064
- # Delete entries by entry ids.
3065
- #
3066
- # @example With splatted entry ids
3067
- # redis.xdel('mystream', '0-1', '0-2')
3068
- # @example With arrayed entry ids
3069
- # redis.xdel('mystream', ['0-1', '0-2'])
3070
- #
3071
- # @param key [String] the stream key
3072
- # @param ids [Array<String>] one or multiple entry ids
3073
- #
3074
- # @return [Integer] the number of entries actually deleted
3075
- def xdel(key, *ids)
3076
- args = [:xdel, key].concat(ids.flatten)
3077
- synchronize { |client| client.call(args) }
3078
- end
3079
-
3080
- # Fetches entries of the stream in ascending order.
3081
- #
3082
- # @example Without options
3083
- # redis.xrange('mystream')
3084
- # @example With a specific start
3085
- # redis.xrange('mystream', '0-1')
3086
- # @example With a specific start and end
3087
- # redis.xrange('mystream', '0-1', '0-3')
3088
- # @example With count options
3089
- # redis.xrange('mystream', count: 10)
3090
- #
3091
- # @param key [String] the stream key
3092
- # @param start [String] first entry id of range, default value is `-`
3093
- # @param end [String] last entry id of range, default value is `+`
3094
- # @param count [Integer] the number of entries as limit
3095
- #
3096
- # @return [Array<Array<String, Hash>>] the ids and entries pairs
3097
- def xrange(key, start = '-', range_end = '+', count: nil)
3098
- args = [:xrange, key, start, range_end]
3099
- args.concat(['COUNT', count]) if count
3100
- synchronize { |client| client.call(args, &HashifyStreamEntries) }
3101
- end
3102
-
3103
- # Fetches entries of the stream in descending order.
3104
- #
3105
- # @example Without options
3106
- # redis.xrevrange('mystream')
3107
- # @example With a specific end
3108
- # redis.xrevrange('mystream', '0-3')
3109
- # @example With a specific end and start
3110
- # redis.xrevrange('mystream', '0-3', '0-1')
3111
- # @example With count options
3112
- # redis.xrevrange('mystream', count: 10)
3113
- #
3114
- # @param key [String] the stream key
3115
- # @param end [String] first entry id of range, default value is `+`
3116
- # @param start [String] last entry id of range, default value is `-`
3117
- # @params count [Integer] the number of entries as limit
3118
- #
3119
- # @return [Array<Array<String, Hash>>] the ids and entries pairs
3120
- def xrevrange(key, range_end = '+', start = '-', count: nil)
3121
- args = [:xrevrange, key, range_end, start]
3122
- args.concat(['COUNT', count]) if count
3123
- synchronize { |client| client.call(args, &HashifyStreamEntries) }
3124
- end
3125
-
3126
- # Returns the number of entries inside a stream.
3127
- #
3128
- # @example With key
3129
- # redis.xlen('mystream')
3130
- #
3131
- # @param key [String] the stream key
3132
- #
3133
- # @return [Integer] the number of entries
3134
- def xlen(key)
3135
- synchronize { |client| client.call([:xlen, key]) }
3136
- end
3137
-
3138
- # Fetches entries from one or multiple streams. Optionally blocking.
3139
- #
3140
- # @example With a key
3141
- # redis.xread('mystream', '0-0')
3142
- # @example With multiple keys
3143
- # redis.xread(%w[mystream1 mystream2], %w[0-0 0-0])
3144
- # @example With count option
3145
- # redis.xread('mystream', '0-0', count: 2)
3146
- # @example With block option
3147
- # redis.xread('mystream', '$', block: 1000)
3148
- #
3149
- # @param keys [Array<String>] one or multiple stream keys
3150
- # @param ids [Array<String>] one or multiple entry ids
3151
- # @param count [Integer] the number of entries as limit per stream
3152
- # @param block [Integer] the number of milliseconds as blocking timeout
3153
- #
3154
- # @return [Hash{String => Hash{String => Hash}}] the entries
3155
- def xread(keys, ids, count: nil, block: nil)
3156
- args = [:xread]
3157
- args << 'COUNT' << count if count
3158
- args << 'BLOCK' << block.to_i if block
3159
- _xread(args, keys, ids, block)
3160
- end
3161
-
3162
- # Manages the consumer group of the stream.
3163
- #
3164
- # @example With `create` subcommand
3165
- # redis.xgroup(:create, 'mystream', 'mygroup', '$')
3166
- # @example With `setid` subcommand
3167
- # redis.xgroup(:setid, 'mystream', 'mygroup', '$')
3168
- # @example With `destroy` subcommand
3169
- # redis.xgroup(:destroy, 'mystream', 'mygroup')
3170
- # @example With `delconsumer` subcommand
3171
- # redis.xgroup(:delconsumer, 'mystream', 'mygroup', 'consumer1')
3172
- #
3173
- # @param subcommand [String] `create` `setid` `destroy` `delconsumer`
3174
- # @param key [String] the stream key
3175
- # @param group [String] the consumer group name
3176
- # @param id_or_consumer [String]
3177
- # * the entry id or `$`, required if subcommand is `create` or `setid`
3178
- # * the consumer name, required if subcommand is `delconsumer`
3179
- # @param mkstream [Boolean] whether to create an empty stream automatically or not
3180
- #
3181
- # @return [String] `OK` if subcommand is `create` or `setid`
3182
- # @return [Integer] effected count if subcommand is `destroy` or `delconsumer`
3183
- def xgroup(subcommand, key, group, id_or_consumer = nil, mkstream: false)
3184
- args = [:xgroup, subcommand, key, group, id_or_consumer, (mkstream ? 'MKSTREAM' : nil)].compact
3185
- synchronize { |client| client.call(args) }
3186
- end
3187
-
3188
- # Fetches a subset of the entries from one or multiple streams related with the consumer group.
3189
- # Optionally blocking.
3190
- #
3191
- # @example With a key
3192
- # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>')
3193
- # @example With multiple keys
3194
- # redis.xreadgroup('mygroup', 'consumer1', %w[mystream1 mystream2], %w[> >])
3195
- # @example With count option
3196
- # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', count: 2)
3197
- # @example With block option
3198
- # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', block: 1000)
3199
- # @example With noack option
3200
- # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', noack: true)
3201
- #
3202
- # @param group [String] the consumer group name
3203
- # @param consumer [String] the consumer name
3204
- # @param keys [Array<String>] one or multiple stream keys
3205
- # @param ids [Array<String>] one or multiple entry ids
3206
- # @param opts [Hash] several options for `XREADGROUP` command
3207
- #
3208
- # @option opts [Integer] :count the number of entries as limit
3209
- # @option opts [Integer] :block the number of milliseconds as blocking timeout
3210
- # @option opts [Boolean] :noack whether message loss is acceptable or not
3211
- #
3212
- # @return [Hash{String => Hash{String => Hash}}] the entries
3213
- def xreadgroup(group, consumer, keys, ids, count: nil, block: nil, noack: nil)
3214
- args = [:xreadgroup, 'GROUP', group, consumer]
3215
- args << 'COUNT' << count if count
3216
- args << 'BLOCK' << block.to_i if block
3217
- args << 'NOACK' if noack
3218
- _xread(args, keys, ids, block)
3219
- end
3220
-
3221
- # Removes one or multiple entries from the pending entries list of a stream consumer group.
3222
- #
3223
- # @example With a entry id
3224
- # redis.xack('mystream', 'mygroup', '1526569495631-0')
3225
- # @example With splatted entry ids
3226
- # redis.xack('mystream', 'mygroup', '0-1', '0-2')
3227
- # @example With arrayed entry ids
3228
- # redis.xack('mystream', 'mygroup', %w[0-1 0-2])
3229
- #
3230
- # @param key [String] the stream key
3231
- # @param group [String] the consumer group name
3232
- # @param ids [Array<String>] one or multiple entry ids
3233
- #
3234
- # @return [Integer] the number of entries successfully acknowledged
3235
- def xack(key, group, *ids)
3236
- args = [:xack, key, group].concat(ids.flatten)
3237
- synchronize { |client| client.call(args) }
3238
- end
3239
-
3240
- # Changes the ownership of a pending entry
3241
- #
3242
- # @example With splatted entry ids
3243
- # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-1', '0-2')
3244
- # @example With arrayed entry ids
3245
- # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2])
3246
- # @example With idle option
3247
- # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], idle: 1000)
3248
- # @example With time option
3249
- # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], time: 1542866959000)
3250
- # @example With retrycount option
3251
- # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], retrycount: 10)
3252
- # @example With force option
3253
- # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], force: true)
3254
- # @example With justid option
3255
- # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], justid: true)
3256
- #
3257
- # @param key [String] the stream key
3258
- # @param group [String] the consumer group name
3259
- # @param consumer [String] the consumer name
3260
- # @param min_idle_time [Integer] the number of milliseconds
3261
- # @param ids [Array<String>] one or multiple entry ids
3262
- # @param opts [Hash] several options for `XCLAIM` command
3263
- #
3264
- # @option opts [Integer] :idle the number of milliseconds as last time it was delivered of the entry
3265
- # @option opts [Integer] :time the number of milliseconds as a specific Unix Epoch time
3266
- # @option opts [Integer] :retrycount the number of retry counter
3267
- # @option opts [Boolean] :force whether to create the pending entry to the pending entries list or not
3268
- # @option opts [Boolean] :justid whether to fetch just an array of entry ids or not
3269
- #
3270
- # @return [Hash{String => Hash}] the entries successfully claimed
3271
- # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
3272
- def xclaim(key, group, consumer, min_idle_time, *ids, **opts)
3273
- args = [:xclaim, key, group, consumer, min_idle_time].concat(ids.flatten)
3274
- args.concat(['IDLE', opts[:idle].to_i]) if opts[:idle]
3275
- args.concat(['TIME', opts[:time].to_i]) if opts[:time]
3276
- args.concat(['RETRYCOUNT', opts[:retrycount]]) if opts[:retrycount]
3277
- args << 'FORCE' if opts[:force]
3278
- args << 'JUSTID' if opts[:justid]
3279
- blk = opts[:justid] ? Noop : HashifyStreamEntries
3280
- synchronize { |client| client.call(args, &blk) }
3281
- end
3282
-
3283
- # Transfers ownership of pending stream entries that match the specified criteria.
3284
- #
3285
- # @example Claim next pending message stuck > 5 minutes and mark as retry
3286
- # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0')
3287
- # @example Claim 50 next pending messages stuck > 5 minutes and mark as retry
3288
- # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', count: 50)
3289
- # @example Claim next pending message stuck > 5 minutes and don't mark as retry
3290
- # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', justid: true)
3291
- # @example Claim next pending message after this id stuck > 5 minutes and mark as retry
3292
- # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '1641321233-0')
3293
- #
3294
- # @param key [String] the stream key
3295
- # @param group [String] the consumer group name
3296
- # @param consumer [String] the consumer name
3297
- # @param min_idle_time [Integer] the number of milliseconds
3298
- # @param start [String] entry id to start scanning from or 0-0 for everything
3299
- # @param count [Integer] number of messages to claim (default 1)
3300
- # @param justid [Boolean] whether to fetch just an array of entry ids or not.
3301
- # Does not increment retry count when true
3302
- #
3303
- # @return [Hash{String => Hash}] the entries successfully claimed
3304
- # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
3305
- def xautoclaim(key, group, consumer, min_idle_time, start, count: nil, justid: false)
3306
- args = [:xautoclaim, key, group, consumer, min_idle_time, start]
3307
- if count
3308
- args << 'COUNT' << count.to_s
3309
- end
3310
- args << 'JUSTID' if justid
3311
- blk = justid ? HashifyStreamAutoclaimJustId : HashifyStreamAutoclaim
3312
- synchronize { |client| client.call(args, &blk) }
3313
- end
3314
-
3315
- # Fetches not acknowledging pending entries
3316
- #
3317
- # @example With key and group
3318
- # redis.xpending('mystream', 'mygroup')
3319
- # @example With range options
3320
- # redis.xpending('mystream', 'mygroup', '-', '+', 10)
3321
- # @example With range and consumer options
3322
- # redis.xpending('mystream', 'mygroup', '-', '+', 10, 'consumer1')
3323
- #
3324
- # @param key [String] the stream key
3325
- # @param group [String] the consumer group name
3326
- # @param start [String] start first entry id of range
3327
- # @param end [String] end last entry id of range
3328
- # @param count [Integer] count the number of entries as limit
3329
- # @param consumer [String] the consumer name
3330
- #
3331
- # @return [Hash] the summary of pending entries
3332
- # @return [Array<Hash>] the pending entries details if options were specified
3333
- def xpending(key, group, *args)
3334
- command_args = [:xpending, key, group]
3335
- case args.size
3336
- when 0, 3, 4
3337
- command_args.concat(args)
3338
- else
3339
- raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 2, 5 or 6)"
3340
- end
3341
-
3342
- summary_needed = args.empty?
3343
- blk = summary_needed ? HashifyStreamPendings : HashifyStreamPendingDetails
3344
- synchronize { |client| client.call(command_args, &blk) }
3345
- end
3346
-
3347
- # Interact with the sentinel command (masters, master, slaves, failover)
3348
- #
3349
- # @param [String] subcommand e.g. `masters`, `master`, `slaves`
3350
- # @param [Array<String>] args depends on subcommand
3351
- # @return [Array<String>, Hash<String, String>, String] depends on subcommand
3352
- def sentinel(subcommand, *args)
3353
- subcommand = subcommand.to_s.downcase
3354
- synchronize do |client|
3355
- client.call([:sentinel, subcommand] + args) do |reply|
3356
- case subcommand
3357
- when "get-master-addr-by-name"
3358
- reply
3359
- else
3360
- if reply.is_a?(Array)
3361
- if reply[0].is_a?(Array)
3362
- reply.map(&Hashify)
3363
- else
3364
- Hashify.call(reply)
3365
- end
3366
- else
3367
- reply
3368
- end
3369
- end
3370
- end
3371
- end
3372
- end
3373
-
3374
- # Sends `CLUSTER *` command to random node and returns its reply.
3375
- #
3376
- # @see https://redis.io/commands#cluster Reference of cluster command
3377
- #
3378
- # @param subcommand [String, Symbol] the subcommand of cluster command
3379
- # e.g. `:slots`, `:nodes`, `:slaves`, `:info`
3380
- #
3381
- # @return [Object] depends on the subcommand
3382
- def cluster(subcommand, *args)
3383
- subcommand = subcommand.to_s.downcase
3384
- block = case subcommand
3385
- when 'slots'
3386
- HashifyClusterSlots
3387
- when 'nodes'
3388
- HashifyClusterNodes
3389
- when 'slaves'
3390
- HashifyClusterSlaves
3391
- when 'info'
3392
- HashifyInfo
3393
- else
3394
- Noop
3395
- end
3396
-
3397
- # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
3398
- block = Noop unless @cluster_mode
3399
-
3400
- synchronize do |client|
3401
- client.call([:cluster, subcommand] + args, &block)
3402
- end
3403
- end
3404
-
3405
- # Sends `ASKING` command to random node and returns its reply.
3406
- #
3407
- # @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
3408
- #
3409
- # @return [String] `'OK'`
3410
- def asking
3411
- synchronize { |client| client.call(%i[asking]) }
3412
- end
3413
-
3414
- def id
3415
- @original_client.id
3416
- end
3417
-
3418
- def inspect
3419
- "#<Redis client v#{Redis::VERSION} for #{id}>"
3420
- end
3421
-
3422
- def dup
3423
- self.class.new(@options)
3424
- end
3425
-
3426
- def connection
3427
- return @original_client.connection_info if @cluster_mode
3428
-
3429
- {
3430
- host: @original_client.host,
3431
- port: @original_client.port,
3432
- db: @original_client.db,
3433
- id: @original_client.id,
3434
- location: @original_client.location
3435
- }
3436
- end
3437
-
3438
- def method_missing(command, *args) # rubocop:disable Style/MissingRespondToMissing
3439
- synchronize do |client|
3440
- client.call([command] + args)
3441
- end
3442
- end
3443
-
3444
- private
3445
-
3446
- # Commands returning 1 for true and 0 for false may be executed in a pipeline
3447
- # where the method call will return nil. Propagate the nil instead of falsely
3448
- # returning false.
3449
- Boolify = lambda { |value|
3450
- case value
3451
- when 1
3452
- true
3453
- when 0
3454
- false
3455
- else
3456
- value
3457
- end
3458
- }
3459
-
3460
- BoolifySet = lambda { |value|
3461
- case value
3462
- when "OK"
3463
- true
3464
- when nil
3465
- false
3466
- else
3467
- value
3468
- end
3469
- }
3470
-
3471
- Hashify = lambda { |value|
3472
- if value.respond_to?(:each_slice)
3473
- value.each_slice(2).to_h
3474
- else
3475
- value
3476
- end
3477
- }
3478
-
3479
- Floatify = lambda { |value|
3480
- case value
3481
- when "inf"
3482
- Float::INFINITY
3483
- when "-inf"
3484
- -Float::INFINITY
3485
- when String
3486
- Float(value)
3487
- else
3488
- value
3489
- end
3490
- }
3491
-
3492
- FloatifyPairs = lambda { |value|
3493
- return value unless value.respond_to?(:each_slice)
3494
-
3495
- value.each_slice(2).map do |member, score|
3496
- [member, Floatify.call(score)]
3497
- end
3498
- }
3499
-
3500
- HashifyInfo = lambda { |reply|
3501
- lines = reply.split("\r\n").grep_v(/^(#|$)/)
3502
- lines.map! { |line| line.split(':', 2) }
3503
- lines.compact!
3504
- lines.to_h
3505
- }
3506
-
3507
- HashifyStreams = lambda { |reply|
3508
- case reply
3509
- when nil
3510
- {}
3511
- else
3512
- reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h
3513
- end
3514
- }
3515
-
3516
- EMPTY_STREAM_RESPONSE = [nil].freeze
3517
- private_constant :EMPTY_STREAM_RESPONSE
3518
-
3519
- HashifyStreamEntries = lambda { |reply|
3520
- reply.compact.map do |entry_id, values|
3521
- [entry_id, values.each_slice(2).to_h]
3522
- end
3523
- }
3524
-
3525
- HashifyStreamAutoclaim = lambda { |reply|
3526
- {
3527
- 'next' => reply[0],
3528
- 'entries' => reply[1].map { |entry| [entry[0], entry[1].each_slice(2).to_h] }
3529
- }
3530
- }
3531
-
3532
- HashifyStreamAutoclaimJustId = lambda { |reply|
3533
- {
3534
- 'next' => reply[0],
3535
- 'entries' => reply[1]
3536
- }
3537
- }
3538
-
3539
- HashifyStreamPendings = lambda { |reply|
3540
- {
3541
- 'size' => reply[0],
3542
- 'min_entry_id' => reply[1],
3543
- 'max_entry_id' => reply[2],
3544
- 'consumers' => reply[3].nil? ? {} : reply[3].to_h
3545
- }
3546
- }
3547
-
3548
- HashifyStreamPendingDetails = lambda { |reply|
3549
- reply.map do |arr|
3550
- {
3551
- 'entry_id' => arr[0],
3552
- 'consumer' => arr[1],
3553
- 'elapsed' => arr[2],
3554
- 'count' => arr[3]
3555
- }
3556
- end
3557
- }
3558
-
3559
- HashifyClusterNodeInfo = lambda { |str|
3560
- arr = str.split(' ')
3561
- {
3562
- 'node_id' => arr[0],
3563
- 'ip_port' => arr[1],
3564
- 'flags' => arr[2].split(','),
3565
- 'master_node_id' => arr[3],
3566
- 'ping_sent' => arr[4],
3567
- 'pong_recv' => arr[5],
3568
- 'config_epoch' => arr[6],
3569
- 'link_state' => arr[7],
3570
- 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
3571
- }
3572
- }
3573
-
3574
- HashifyClusterSlots = lambda { |reply|
3575
- reply.map do |arr|
3576
- first_slot, last_slot = arr[0..1]
3577
- master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
3578
- replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
3579
- {
3580
- 'start_slot' => first_slot,
3581
- 'end_slot' => last_slot,
3582
- 'master' => master,
3583
- 'replicas' => replicas
3584
- }
3585
- end
3586
- }
3587
-
3588
- HashifyClusterNodes = lambda { |reply|
3589
- reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
3590
- }
3591
-
3592
- HashifyClusterSlaves = lambda { |reply|
3593
- reply.map { |str| HashifyClusterNodeInfo.call(str) }
3594
- }
3595
-
3596
- Noop = ->(reply) { reply }
3597
-
3598
- def _geoarguments(*args, options: nil, sort: nil, count: nil)
3599
- args.push sort if sort
3600
- args.push 'count', count if count
3601
- args.push options if options
3602
- args
3603
- end
3604
-
3605
- def _subscription(method, timeout, channels, block)
3606
- return @client.call([method] + channels) if subscribed?
3607
-
3608
- begin
3609
- original, @client = @client, SubscribedClient.new(@client)
3610
- if timeout > 0
3611
- @client.send(method, timeout, *channels, &block)
3612
- else
3613
- @client.send(method, *channels, &block)
283
+ begin
284
+ original, @client = @client, SubscribedClient.new(@client)
285
+ if timeout > 0
286
+ @client.send(method, timeout, *channels, &block)
287
+ else
288
+ @client.send(method, *channels, &block)
3614
289
  end
3615
290
  ensure
3616
291
  @client = original
3617
292
  end
3618
293
  end
3619
-
3620
- def _xread(args, keys, ids, blocking_timeout_msec)
3621
- keys = keys.is_a?(Array) ? keys : [keys]
3622
- ids = ids.is_a?(Array) ? ids : [ids]
3623
- args << 'STREAMS'
3624
- args.concat(keys)
3625
- args.concat(ids)
3626
-
3627
- synchronize do |client|
3628
- if blocking_timeout_msec.nil?
3629
- client.call(args, &HashifyStreams)
3630
- elsif blocking_timeout_msec.to_f.zero?
3631
- client.call_without_timeout(args, &HashifyStreams)
3632
- else
3633
- timeout = client.timeout.to_f + blocking_timeout_msec.to_f / 1000.0
3634
- client.call_with_timeout(args, timeout, &HashifyStreams)
3635
- end
3636
- end
3637
- end
3638
294
  end
3639
295
 
3640
- require_relative "redis/version"
3641
- require_relative "redis/connection"
3642
- require_relative "redis/client"
3643
- require_relative "redis/cluster"
3644
- require_relative "redis/pipeline"
3645
- require_relative "redis/subscribe"
296
+ require "redis/version"
297
+ require "redis/connection"
298
+ require "redis/client"
299
+ require "redis/cluster"
300
+ require "redis/pipeline"
301
+ require "redis/subscribe"