redis 4.1.4 → 4.7.1

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