redis 4.2.5 → 5.0.7

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