redis 4.4.0 → 5.0.7

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