redis 4.1.0 → 4.6.0

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