redis 4.5.1 → 4.6.0

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