redis 4.5.1 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
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"