redis 4.0.0.rc1 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +164 -3
  3. data/README.md +127 -18
  4. data/lib/redis/client.rb +164 -93
  5. data/lib/redis/cluster/command.rb +81 -0
  6. data/lib/redis/cluster/command_loader.rb +33 -0
  7. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  8. data/lib/redis/cluster/node.rb +108 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +37 -0
  11. data/lib/redis/cluster/option.rb +93 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +49 -0
  14. data/lib/redis/cluster.rb +291 -0
  15. data/lib/redis/connection/command_helper.rb +5 -2
  16. data/lib/redis/connection/hiredis.rb +4 -3
  17. data/lib/redis/connection/registry.rb +2 -1
  18. data/lib/redis/connection/ruby.rb +125 -106
  19. data/lib/redis/connection/synchrony.rb +18 -5
  20. data/lib/redis/connection.rb +2 -0
  21. data/lib/redis/distributed.rb +995 -0
  22. data/lib/redis/errors.rb +48 -0
  23. data/lib/redis/hash_ring.rb +89 -0
  24. data/lib/redis/pipeline.rb +55 -9
  25. data/lib/redis/subscribe.rb +11 -12
  26. data/lib/redis/version.rb +3 -1
  27. data/lib/redis.rb +1433 -381
  28. metadata +34 -141
  29. data/.gitignore +0 -16
  30. data/.travis/Gemfile +0 -11
  31. data/.travis.yml +0 -71
  32. data/.yardopts +0 -3
  33. data/Gemfile +0 -3
  34. data/benchmarking/logging.rb +0 -71
  35. data/benchmarking/pipeline.rb +0 -51
  36. data/benchmarking/speed.rb +0 -21
  37. data/benchmarking/suite.rb +0 -24
  38. data/benchmarking/worker.rb +0 -71
  39. data/examples/basic.rb +0 -15
  40. data/examples/consistency.rb +0 -114
  41. data/examples/incr-decr.rb +0 -17
  42. data/examples/list.rb +0 -26
  43. data/examples/pubsub.rb +0 -37
  44. data/examples/sentinel/sentinel.conf +0 -9
  45. data/examples/sentinel/start +0 -49
  46. data/examples/sentinel.rb +0 -41
  47. data/examples/sets.rb +0 -36
  48. data/examples/unicorn/config.ru +0 -3
  49. data/examples/unicorn/unicorn.rb +0 -20
  50. data/makefile +0 -42
  51. data/redis.gemspec +0 -40
  52. data/test/bitpos_test.rb +0 -63
  53. data/test/blocking_commands_test.rb +0 -183
  54. data/test/client_test.rb +0 -59
  55. data/test/command_map_test.rb +0 -28
  56. data/test/commands_on_hashes_test.rb +0 -174
  57. data/test/commands_on_hyper_log_log_test.rb +0 -70
  58. data/test/commands_on_lists_test.rb +0 -154
  59. data/test/commands_on_sets_test.rb +0 -208
  60. data/test/commands_on_sorted_sets_test.rb +0 -444
  61. data/test/commands_on_strings_test.rb +0 -338
  62. data/test/commands_on_value_types_test.rb +0 -246
  63. data/test/connection_handling_test.rb +0 -275
  64. data/test/db/.gitkeep +0 -0
  65. data/test/encoding_test.rb +0 -14
  66. data/test/error_replies_test.rb +0 -57
  67. data/test/fork_safety_test.rb +0 -60
  68. data/test/helper.rb +0 -179
  69. data/test/helper_test.rb +0 -22
  70. data/test/internals_test.rb +0 -435
  71. data/test/persistence_control_commands_test.rb +0 -24
  72. data/test/pipelining_commands_test.rb +0 -238
  73. data/test/publish_subscribe_test.rb +0 -280
  74. data/test/remote_server_control_commands_test.rb +0 -175
  75. data/test/scanning_test.rb +0 -407
  76. data/test/scripting_test.rb +0 -76
  77. data/test/sentinel_command_test.rb +0 -78
  78. data/test/sentinel_test.rb +0 -253
  79. data/test/sorting_test.rb +0 -57
  80. data/test/ssl_test.rb +0 -69
  81. data/test/support/connection/hiredis.rb +0 -1
  82. data/test/support/connection/ruby.rb +0 -1
  83. data/test/support/connection/synchrony.rb +0 -17
  84. data/test/support/redis_mock.rb +0 -130
  85. data/test/support/ssl/gen_certs.sh +0 -31
  86. data/test/support/ssl/trusted-ca.crt +0 -25
  87. data/test/support/ssl/trusted-ca.key +0 -27
  88. data/test/support/ssl/trusted-cert.crt +0 -81
  89. data/test/support/ssl/trusted-cert.key +0 -28
  90. data/test/support/ssl/untrusted-ca.crt +0 -26
  91. data/test/support/ssl/untrusted-ca.key +0 -27
  92. data/test/support/ssl/untrusted-cert.crt +0 -82
  93. data/test/support/ssl/untrusted-cert.key +0 -28
  94. data/test/support/wire/synchrony.rb +0 -24
  95. data/test/support/wire/thread.rb +0 -5
  96. data/test/synchrony_driver.rb +0 -85
  97. data/test/test.conf.erb +0 -9
  98. data/test/thread_safety_test.rb +0 -60
  99. data/test/transactions_test.rb +0 -262
  100. data/test/unknown_commands_test.rb +0 -12
  101. data/test/url_param_test.rb +0 -136
data/lib/redis.rb CHANGED
@@ -1,14 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "monitor"
2
4
  require_relative "redis/errors"
3
5
 
4
6
  class Redis
7
+ @exists_returns_integer = true
5
8
 
6
- def self.current
7
- @current ||= Redis.new
9
+ class << self
10
+ attr_reader :exists_returns_integer
11
+
12
+ def exists_returns_integer=(value)
13
+ unless value
14
+ message = "`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly " \
15
+ "disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use " \
16
+ "`exists?` instead."
17
+
18
+ ::Kernel.warn(message)
19
+ end
20
+
21
+ @exists_returns_integer = value
22
+ end
23
+
24
+ attr_writer :current
8
25
  end
9
26
 
10
- def self.current=(redis)
11
- @current = redis
27
+ def self.current
28
+ @current ||= Redis.new
12
29
  end
13
30
 
14
31
  include MonitorMixin
@@ -16,26 +33,36 @@ class Redis
16
33
  # Create a new client instance
17
34
  #
18
35
  # @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.
36
+ # @option options [String] :url (value of the environment variable REDIS_URL) a Redis URL, for a TCP connection:
37
+ # `redis://:[password]@[hostname]:[port]/[db]` (password, port and database are optional), for a unix socket
38
+ # connection: `unix://[path to Redis socket]`. This overrides all other options.
20
39
  # @option options [String] :host ("127.0.0.1") server hostname
21
- # @option options [Fixnum] :port (6379) server port
40
+ # @option options [Integer] :port (6379) server port
22
41
  # @option options [String] :path path to server socket (overrides host and port)
23
42
  # @option options [Float] :timeout (5.0) timeout in seconds
24
43
  # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
44
+ # @option options [String] :username Username to authenticate against server
25
45
  # @option options [String] :password Password to authenticate against server
26
- # @option options [Fixnum] :db (0) Database to select after initial connect
46
+ # @option options [Integer] :db (0) Database to select after initial connect
27
47
  # @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
48
+ # @option options [String] :id ID for the client connection, assigns name to current connection by sending
49
+ # `CLIENT SETNAME`
50
+ # @option options [Hash, Integer] :tcp_keepalive Keepalive values, if Integer `intvl` and `probe` are calculated
51
+ # based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Integer
52
+ # @option options [Integer] :reconnect_attempts Number of attempts trying to connect
31
53
  # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
32
54
  # @option options [Array] :sentinels List of sentinels to contact
33
55
  # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
56
+ # @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
57
+ # @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
58
+ # @option options [Class] :connector Class of custom connector
34
59
  #
35
60
  # @return [Redis] a new client instance
36
61
  def initialize(options = {})
37
62
  @options = options.dup
38
- @original_client = @client = Client.new(options)
63
+ @cluster_mode = options.key?(:cluster)
64
+ client = @cluster_mode ? Cluster : Client
65
+ @original_client = @client = client.new(options)
39
66
  @queue = Hash.new { |h, k| h[k] = [] }
40
67
 
41
68
  super() # Monitor#initialize
@@ -46,7 +73,7 @@ class Redis
46
73
  end
47
74
 
48
75
  # Run code with the client reconnecting
49
- def with_reconnect(val=true, &blk)
76
+ def with_reconnect(val = true, &blk)
50
77
  synchronize do |client|
51
78
  client.with_reconnect(val, &blk)
52
79
  end
@@ -89,7 +116,9 @@ class Redis
89
116
  # See http://redis.io/topics/pipelining for more details.
90
117
  #
91
118
  def queue(*command)
92
- @queue[Thread.current.object_id] << command
119
+ synchronize do
120
+ @queue[Thread.current.object_id] << command
121
+ end
93
122
  end
94
123
 
95
124
  # Sends all commands in the queue.
@@ -99,7 +128,12 @@ class Redis
99
128
  def commit
100
129
  synchronize do |client|
101
130
  begin
102
- client.call_pipelined(@queue[Thread.current.object_id])
131
+ pipeline = Pipeline.new(client)
132
+ @queue[Thread.current.object_id].each do |command|
133
+ pipeline.call(command)
134
+ end
135
+
136
+ client.call_pipelined(pipeline)
103
137
  ensure
104
138
  @queue.delete(Thread.current.object_id)
105
139
  end
@@ -112,18 +146,19 @@ class Redis
112
146
 
113
147
  # Authenticate to the server.
114
148
  #
115
- # @param [String] password must match the password specified in the
116
- # `requirepass` directive in the configuration file
149
+ # @param [Array<String>] args includes both username and password
150
+ # or only password
117
151
  # @return [String] `OK`
118
- def auth(password)
152
+ # @see https://redis.io/commands/auth AUTH command
153
+ def auth(*args)
119
154
  synchronize do |client|
120
- client.call([:auth, password])
155
+ client.call([:auth, *args])
121
156
  end
122
157
  end
123
158
 
124
159
  # Change the selected database for the current connection.
125
160
  #
126
- # @param [Fixnum] db zero-based index of the DB to use (0 to 15)
161
+ # @param [Integer] db zero-based index of the DB to use (0 to 15)
127
162
  # @return [String] `OK`
128
163
  def select(db)
129
164
  synchronize do |client|
@@ -134,10 +169,11 @@ class Redis
134
169
 
135
170
  # Ping the server.
136
171
  #
172
+ # @param [optional, String] message
137
173
  # @return [String] `PONG`
138
- def ping
174
+ def ping(message = nil)
139
175
  synchronize do |client|
140
- client.call([:ping])
176
+ client.call([:ping, message].compact)
141
177
  end
142
178
  end
143
179
 
@@ -191,7 +227,7 @@ class Redis
191
227
  def config(action, *args)
192
228
  synchronize do |client|
193
229
  client.call([:config, action] + args) do |reply|
194
- if reply.kind_of?(Array) && action == :get
230
+ if reply.is_a?(Array) && action == :get
195
231
  Hashify.call(reply)
196
232
  else
197
233
  reply
@@ -221,7 +257,7 @@ class Redis
221
257
 
222
258
  # Return the number of keys in the selected database.
223
259
  #
224
- # @return [Fixnum]
260
+ # @return [Integer]
225
261
  def dbsize
226
262
  synchronize do |client|
227
263
  client.call([:dbsize])
@@ -236,19 +272,31 @@ class Redis
236
272
 
237
273
  # Remove all keys from all databases.
238
274
  #
275
+ # @param [Hash] options
276
+ # - `:async => Boolean`: async flush (default: false)
239
277
  # @return [String] `OK`
240
- def flushall
278
+ def flushall(options = nil)
241
279
  synchronize do |client|
242
- client.call([:flushall])
280
+ if options && options[:async]
281
+ client.call(%i[flushall async])
282
+ else
283
+ client.call([:flushall])
284
+ end
243
285
  end
244
286
  end
245
287
 
246
288
  # Remove all keys from the current database.
247
289
  #
290
+ # @param [Hash] options
291
+ # - `:async => Boolean`: async flush (default: false)
248
292
  # @return [String] `OK`
249
- def flushdb
293
+ def flushdb(options = nil)
250
294
  synchronize do |client|
251
- client.call([:flushdb])
295
+ if options && options[:async]
296
+ client.call(%i[flushdb async])
297
+ else
298
+ client.call([:flushdb])
299
+ end
252
300
  end
253
301
  end
254
302
 
@@ -259,10 +307,8 @@ class Redis
259
307
  def info(cmd = nil)
260
308
  synchronize do |client|
261
309
  client.call([:info, cmd].compact) do |reply|
262
- if reply.kind_of?(String)
263
- reply = Hash[reply.split("\r\n").map do |line|
264
- line.split(":", 2) unless line =~ /^(#|$)/
265
- end.compact]
310
+ if reply.is_a?(String)
311
+ reply = HashifyInfo.call(reply)
266
312
 
267
313
  if cmd && cmd.to_s == "commandstats"
268
314
  # Extract nested hashes for INFO COMMANDSTATS
@@ -280,7 +326,7 @@ class Redis
280
326
 
281
327
  # Get the UNIX time stamp of the last successful save to disk.
282
328
  #
283
- # @return [Fixnum]
329
+ # @return [Integer]
284
330
  def lastsave
285
331
  synchronize do |client|
286
332
  client.call([:lastsave])
@@ -332,9 +378,9 @@ class Redis
332
378
  # Interact with the slowlog (get, len, reset)
333
379
  #
334
380
  # @param [String] subcommand e.g. `get`, `len`, `reset`
335
- # @param [Fixnum] length maximum number of entries to return
336
- # @return [Array<String>, Fixnum, String] depends on subcommand
337
- def slowlog(subcommand, length=nil)
381
+ # @param [Integer] length maximum number of entries to return
382
+ # @return [Array<String>, Integer, String] depends on subcommand
383
+ def slowlog(subcommand, length = nil)
338
384
  synchronize do |client|
339
385
  args = [:slowlog, subcommand]
340
386
  args << length if length
@@ -354,12 +400,12 @@ class Redis
354
400
  # @example
355
401
  # r.time # => [ 1333093196, 606806 ]
356
402
  #
357
- # @return [Array<Fixnum>] tuple of seconds since UNIX epoch and
403
+ # @return [Array<Integer>] tuple of seconds since UNIX epoch and
358
404
  # microseconds in the current second
359
405
  def time
360
406
  synchronize do |client|
361
407
  client.call([:time]) do |reply|
362
- reply.map(&:to_i) if reply
408
+ reply&.map(&:to_i)
363
409
  end
364
410
  end
365
411
  end
@@ -377,7 +423,7 @@ class Redis
377
423
  # Set a key's time to live in seconds.
378
424
  #
379
425
  # @param [String] key
380
- # @param [Fixnum] seconds time to live
426
+ # @param [Integer] seconds time to live
381
427
  # @return [Boolean] whether the timeout was set or not
382
428
  def expire(key, seconds)
383
429
  synchronize do |client|
@@ -388,7 +434,7 @@ class Redis
388
434
  # Set the expiration for a key as a UNIX timestamp.
389
435
  #
390
436
  # @param [String] key
391
- # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp
437
+ # @param [Integer] unix_time expiry time specified as a UNIX timestamp
392
438
  # @return [Boolean] whether the timeout was set or not
393
439
  def expireat(key, unix_time)
394
440
  synchronize do |client|
@@ -399,7 +445,7 @@ class Redis
399
445
  # Get the time to live (in seconds) for a key.
400
446
  #
401
447
  # @param [String] key
402
- # @return [Fixnum] remaining time to live in seconds.
448
+ # @return [Integer] remaining time to live in seconds.
403
449
  #
404
450
  # In Redis 2.6 or older the command returns -1 if the key does not exist or if
405
451
  # the key exist but has no associated expire.
@@ -417,7 +463,7 @@ class Redis
417
463
  # Set a key's time to live in milliseconds.
418
464
  #
419
465
  # @param [String] key
420
- # @param [Fixnum] milliseconds time to live
466
+ # @param [Integer] milliseconds time to live
421
467
  # @return [Boolean] whether the timeout was set or not
422
468
  def pexpire(key, milliseconds)
423
469
  synchronize do |client|
@@ -428,7 +474,7 @@ class Redis
428
474
  # Set the expiration for a key as number of milliseconds from UNIX Epoch.
429
475
  #
430
476
  # @param [String] key
431
- # @param [Fixnum] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
477
+ # @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
432
478
  # @return [Boolean] whether the timeout was set or not
433
479
  def pexpireat(key, ms_unix_time)
434
480
  synchronize do |client|
@@ -439,7 +485,7 @@ class Redis
439
485
  # Get the time to live (in milliseconds) for a key.
440
486
  #
441
487
  # @param [String] key
442
- # @return [Fixnum] remaining time to live in milliseconds
488
+ # @return [Integer] remaining time to live in milliseconds
443
489
  # In Redis 2.6 or older the command returns -1 if the key does not exist or if
444
490
  # the key exist but has no associated expire.
445
491
  #
@@ -468,50 +514,104 @@ class Redis
468
514
  # @param [String] key
469
515
  # @param [String] ttl
470
516
  # @param [String] serialized_value
517
+ # @param [Hash] options
518
+ # - `:replace => Boolean`: if false, raises an error if key already exists
519
+ # @raise [Redis::CommandError]
471
520
  # @return [String] `"OK"`
472
- def restore(key, ttl, serialized_value)
521
+ def restore(key, ttl, serialized_value, replace: nil)
522
+ args = [:restore, key, ttl, serialized_value]
523
+ args << 'REPLACE' if replace
524
+
473
525
  synchronize do |client|
474
- client.call([:restore, key, ttl, serialized_value])
526
+ client.call(args)
475
527
  end
476
528
  end
477
529
 
478
530
  # Transfer a key from the connected instance to another instance.
479
531
  #
480
- # @param [String] key
532
+ # @param [String, Array<String>] key
481
533
  # @param [Hash] options
482
534
  # - `:host => String`: host of instance to migrate to
483
535
  # - `:port => Integer`: port of instance to migrate to
484
536
  # - `:db => Integer`: database to migrate to (default: same as source)
485
537
  # - `:timeout => Integer`: timeout (default: same as connection timeout)
538
+ # - `:copy => Boolean`: Do not remove the key from the local instance.
539
+ # - `:replace => Boolean`: Replace existing key on the remote instance.
486
540
  # @return [String] `"OK"`
487
541
  def migrate(key, options)
488
- host = options[:host] || raise(RuntimeError, ":host not specified")
489
- port = options[:port] || raise(RuntimeError, ":port not specified")
490
- db = (options[:db] || @client.db).to_i
491
- timeout = (options[:timeout] || @client.timeout).to_i
542
+ args = [:migrate]
543
+ args << (options[:host] || raise(':host not specified'))
544
+ args << (options[:port] || raise(':port not specified'))
545
+ args << (key.is_a?(String) ? key : '')
546
+ args << (options[:db] || @client.db).to_i
547
+ args << (options[:timeout] || @client.timeout).to_i
548
+ args << 'COPY' if options[:copy]
549
+ args << 'REPLACE' if options[:replace]
550
+ args += ['KEYS', *key] if key.is_a?(Array)
492
551
 
493
- synchronize do |client|
494
- client.call([:migrate, host, port, key, db, timeout])
495
- end
552
+ synchronize { |client| client.call(args) }
496
553
  end
497
554
 
498
555
  # Delete one or more keys.
499
556
  #
500
557
  # @param [String, Array<String>] keys
501
- # @return [Fixnum] number of keys that were deleted
558
+ # @return [Integer] number of keys that were deleted
502
559
  def del(*keys)
560
+ keys.flatten!(1)
561
+ return 0 if keys.empty?
562
+
503
563
  synchronize do |client|
504
564
  client.call([:del] + keys)
505
565
  end
506
566
  end
507
567
 
508
- # Determine if a key exists.
568
+ # Unlink one or more keys.
509
569
  #
510
- # @param [String] key
570
+ # @param [String, Array<String>] keys
571
+ # @return [Integer] number of keys that were unlinked
572
+ def unlink(*keys)
573
+ synchronize do |client|
574
+ client.call([:unlink] + keys)
575
+ end
576
+ end
577
+
578
+ # Determine how many of the keys exists.
579
+ #
580
+ # @param [String, Array<String>] keys
581
+ # @return [Integer]
582
+ def exists(*keys)
583
+ if !Redis.exists_returns_integer && keys.size == 1
584
+ if Redis.exists_returns_integer.nil?
585
+ message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
586
+ "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
587
+ "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
588
+ "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0. " \
589
+ "(#{::Kernel.caller(1, 1).first})\n"
590
+
591
+ ::Kernel.warn(message)
592
+ end
593
+
594
+ exists?(*keys)
595
+ else
596
+ _exists(*keys)
597
+ end
598
+ end
599
+
600
+ def _exists(*keys)
601
+ synchronize do |client|
602
+ client.call([:exists, *keys])
603
+ end
604
+ end
605
+
606
+ # Determine if any of the keys exists.
607
+ #
608
+ # @param [String, Array<String>] keys
511
609
  # @return [Boolean]
512
- def exists(key)
610
+ def exists?(*keys)
513
611
  synchronize do |client|
514
- client.call([:exists, key], &Boolify)
612
+ client.call([:exists, *keys]) do |value|
613
+ value > 0
614
+ end
515
615
  end
516
616
  end
517
617
 
@@ -522,7 +622,7 @@ class Redis
522
622
  def keys(pattern = "*")
523
623
  synchronize do |client|
524
624
  client.call([:keys, pattern]) do |reply|
525
- if reply.kind_of?(String)
625
+ if reply.is_a?(String)
526
626
  reply.split(" ")
527
627
  else
528
628
  reply
@@ -548,7 +648,7 @@ class Redis
548
648
  # # => "bar"
549
649
  #
550
650
  # @param [String] key
551
- # @param [Fixnum] db
651
+ # @param [Integer] db
552
652
  # @return [Boolean] whether the key was moved or not
553
653
  def move(key, db)
554
654
  synchronize do |client|
@@ -612,36 +712,33 @@ class Redis
612
712
  # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
613
713
  # - `:store => String`: key to store the result at
614
714
  #
615
- # @return [Array<String>, Array<Array<String>>, Fixnum]
715
+ # @return [Array<String>, Array<Array<String>>, Integer]
616
716
  # - when `:get` is not specified, or holds a single element, an array of elements
617
717
  # - when `:get` is specified, and holds more than one element, an array of
618
718
  # elements where every element is an array with the result for every
619
719
  # element specified in `:get`
620
720
  # - when `:store` is specified, the number of elements in the stored result
621
- def sort(key, options = {})
622
- args = []
623
-
624
- by = options[:by]
625
- args.concat(["BY", by]) if by
721
+ def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
722
+ args = [:sort, key]
723
+ args << "BY" << by if by
626
724
 
627
- limit = options[:limit]
628
- args.concat(["LIMIT"] + limit) if limit
725
+ if limit
726
+ args << "LIMIT"
727
+ args.concat(limit)
728
+ end
629
729
 
630
- get = Array(options[:get])
631
- args.concat(["GET"].product(get).flatten) unless get.empty?
730
+ get = Array(get)
731
+ get.each do |item|
732
+ args << "GET" << item
733
+ end
632
734
 
633
- order = options[:order]
634
735
  args.concat(order.split(" ")) if order
635
-
636
- store = options[:store]
637
- args.concat(["STORE", store]) if store
736
+ args << "STORE" << store if store
638
737
 
639
738
  synchronize do |client|
640
- client.call([:sort, key] + args) do |reply|
739
+ client.call(args) do |reply|
641
740
  if get.size > 1 && !store
642
- if reply
643
- reply.each_slice(get.size).to_a
644
- end
741
+ reply.each_slice(get.size).to_a if reply
645
742
  else
646
743
  reply
647
744
  end
@@ -666,7 +763,7 @@ class Redis
666
763
  # # => 4
667
764
  #
668
765
  # @param [String] key
669
- # @return [Fixnum] value after decrementing it
766
+ # @return [Integer] value after decrementing it
670
767
  def decr(key)
671
768
  synchronize do |client|
672
769
  client.call([:decr, key])
@@ -680,8 +777,8 @@ class Redis
680
777
  # # => 0
681
778
  #
682
779
  # @param [String] key
683
- # @param [Fixnum] decrement
684
- # @return [Fixnum] value after decrementing it
780
+ # @param [Integer] decrement
781
+ # @return [Integer] value after decrementing it
685
782
  def decrby(key, decrement)
686
783
  synchronize do |client|
687
784
  client.call([:decrby, key, decrement])
@@ -695,7 +792,7 @@ class Redis
695
792
  # # => 6
696
793
  #
697
794
  # @param [String] key
698
- # @return [Fixnum] value after incrementing it
795
+ # @return [Integer] value after incrementing it
699
796
  def incr(key)
700
797
  synchronize do |client|
701
798
  client.call([:incr, key])
@@ -709,8 +806,8 @@ class Redis
709
806
  # # => 10
710
807
  #
711
808
  # @param [String] key
712
- # @param [Fixnum] increment
713
- # @return [Fixnum] value after incrementing it
809
+ # @param [Integer] increment
810
+ # @return [Integer] value after incrementing it
714
811
  def incrby(key, increment)
715
812
  synchronize do |client|
716
813
  client.call([:incrby, key, increment])
@@ -737,31 +834,31 @@ class Redis
737
834
  # @param [String] key
738
835
  # @param [String] value
739
836
  # @param [Hash] options
740
- # - `:ex => Fixnum`: Set the specified expire time, in seconds.
741
- # - `:px => Fixnum`: Set the specified expire time, in milliseconds.
837
+ # - `:ex => Integer`: Set the specified expire time, in seconds.
838
+ # - `:px => Integer`: Set the specified expire time, in milliseconds.
839
+ # - `:exat => Integer` : Set the specified Unix time at which the key will expire, in seconds.
840
+ # - `:pxat => Integer` : Set the specified Unix time at which the key will expire, in milliseconds.
742
841
  # - `:nx => true`: Only set the key if it does not already exist.
743
842
  # - `:xx => true`: Only set the key if it already exist.
843
+ # - `:keepttl => true`: Retain the time to live associated with the key.
844
+ # - `:get => true`: Return the old string stored at key, or nil if key did not exist.
744
845
  # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
745
- def set(key, value, options = {})
746
- args = []
747
-
748
- ex = options[:ex]
749
- args.concat(["EX", ex]) if ex
750
-
751
- px = options[:px]
752
- args.concat(["PX", px]) if px
753
-
754
- nx = options[:nx]
755
- args.concat(["NX"]) if nx
756
-
757
- xx = options[:xx]
758
- args.concat(["XX"]) if xx
846
+ def set(key, value, ex: nil, px: nil, exat: nil, pxat: nil, nx: nil, xx: nil, keepttl: nil, get: nil)
847
+ args = [:set, key, value.to_s]
848
+ args << "EX" << ex if ex
849
+ args << "PX" << px if px
850
+ args << "EXAT" << exat if exat
851
+ args << "PXAT" << pxat if pxat
852
+ args << "NX" if nx
853
+ args << "XX" if xx
854
+ args << "KEEPTTL" if keepttl
855
+ args << "GET" if get
759
856
 
760
857
  synchronize do |client|
761
858
  if nx || xx
762
- client.call([:set, key, value.to_s] + args, &BoolifySet)
859
+ client.call(args, &BoolifySet)
763
860
  else
764
- client.call([:set, key, value.to_s] + args)
861
+ client.call(args)
765
862
  end
766
863
  end
767
864
  end
@@ -769,7 +866,7 @@ class Redis
769
866
  # Set the time to live in seconds of a key.
770
867
  #
771
868
  # @param [String] key
772
- # @param [Fixnum] ttl
869
+ # @param [Integer] ttl
773
870
  # @param [String] value
774
871
  # @return [String] `"OK"`
775
872
  def setex(key, ttl, value)
@@ -781,7 +878,7 @@ class Redis
781
878
  # Set the time to live in milliseconds of a key.
782
879
  #
783
880
  # @param [String] key
784
- # @param [Fixnum] ttl
881
+ # @param [Integer] ttl
785
882
  # @param [String] value
786
883
  # @return [String] `"OK"`
787
884
  def psetex(key, ttl, value)
@@ -843,7 +940,7 @@ class Redis
843
940
  # @see #mapped_msetnx
844
941
  def msetnx(*args)
845
942
  synchronize do |client|
846
- client.call([:msetnx] + args, &Boolify)
943
+ client.call([:msetnx, *args], &Boolify)
847
944
  end
848
945
  end
849
946
 
@@ -874,7 +971,7 @@ class Redis
874
971
  # Get the values of all the given keys.
875
972
  #
876
973
  # @example
877
- # redis.mget("key1", "key1")
974
+ # redis.mget("key1", "key2")
878
975
  # # => ["v1", "v2"]
879
976
  #
880
977
  # @param [Array<String>] keys
@@ -883,7 +980,7 @@ class Redis
883
980
  # @see #mapped_mget
884
981
  def mget(*keys, &blk)
885
982
  synchronize do |client|
886
- client.call([:mget] + keys, &blk)
983
+ client.call([:mget, *keys], &blk)
887
984
  end
888
985
  end
889
986
 
@@ -899,7 +996,7 @@ class Redis
899
996
  # @see #mget
900
997
  def mapped_mget(*keys)
901
998
  mget(*keys) do |reply|
902
- if reply.kind_of?(Array)
999
+ if reply.is_a?(Array)
903
1000
  Hash[keys.zip(reply)]
904
1001
  else
905
1002
  reply
@@ -910,9 +1007,9 @@ class Redis
910
1007
  # Overwrite part of a string at key starting at the specified offset.
911
1008
  #
912
1009
  # @param [String] key
913
- # @param [Fixnum] offset byte offset
1010
+ # @param [Integer] offset byte offset
914
1011
  # @param [String] value
915
- # @return [Fixnum] length of the string after it was modified
1012
+ # @return [Integer] length of the string after it was modified
916
1013
  def setrange(key, offset, value)
917
1014
  synchronize do |client|
918
1015
  client.call([:setrange, key, offset, value.to_s])
@@ -922,10 +1019,10 @@ class Redis
922
1019
  # Get a substring of the string stored at a key.
923
1020
  #
924
1021
  # @param [String] key
925
- # @param [Fixnum] start zero-based start offset
926
- # @param [Fixnum] stop zero-based end offset. Use -1 for representing
1022
+ # @param [Integer] start zero-based start offset
1023
+ # @param [Integer] stop zero-based end offset. Use -1 for representing
927
1024
  # the end of the string
928
- # @return [Fixnum] `0` or `1`
1025
+ # @return [Integer] `0` or `1`
929
1026
  def getrange(key, start, stop)
930
1027
  synchronize do |client|
931
1028
  client.call([:getrange, key, start, stop])
@@ -935,9 +1032,9 @@ class Redis
935
1032
  # Sets or clears the bit at offset in the string value stored at key.
936
1033
  #
937
1034
  # @param [String] key
938
- # @param [Fixnum] offset bit offset
939
- # @param [Fixnum] value bit value `0` or `1`
940
- # @return [Fixnum] the original bit value stored at `offset`
1035
+ # @param [Integer] offset bit offset
1036
+ # @param [Integer] value bit value `0` or `1`
1037
+ # @return [Integer] the original bit value stored at `offset`
941
1038
  def setbit(key, offset, value)
942
1039
  synchronize do |client|
943
1040
  client.call([:setbit, key, offset, value])
@@ -947,8 +1044,8 @@ class Redis
947
1044
  # Returns the bit value at offset in the string value stored at key.
948
1045
  #
949
1046
  # @param [String] key
950
- # @param [Fixnum] offset bit offset
951
- # @return [Fixnum] `0` or `1`
1047
+ # @param [Integer] offset bit offset
1048
+ # @return [Integer] `0` or `1`
952
1049
  def getbit(key, offset)
953
1050
  synchronize do |client|
954
1051
  client.call([:getbit, key, offset])
@@ -959,7 +1056,7 @@ class Redis
959
1056
  #
960
1057
  # @param [String] key
961
1058
  # @param [String] value value to append
962
- # @return [Fixnum] length of the string after appending
1059
+ # @return [Integer] length of the string after appending
963
1060
  def append(key, value)
964
1061
  synchronize do |client|
965
1062
  client.call([:append, key, value])
@@ -969,9 +1066,9 @@ class Redis
969
1066
  # Count the number of set bits in a range of the string value stored at key.
970
1067
  #
971
1068
  # @param [String] key
972
- # @param [Fixnum] start start index
973
- # @param [Fixnum] stop stop index
974
- # @return [Fixnum] the number of bits set to 1
1069
+ # @param [Integer] start start index
1070
+ # @param [Integer] stop stop index
1071
+ # @return [Integer] the number of bits set to 1
975
1072
  def bitcount(key, start = 0, stop = -1)
976
1073
  synchronize do |client|
977
1074
  client.call([:bitcount, key, start, stop])
@@ -983,25 +1080,23 @@ class Redis
983
1080
  # @param [String] operation e.g. `and`, `or`, `xor`, `not`
984
1081
  # @param [String] destkey destination key
985
1082
  # @param [String, Array<String>] keys one or more source keys to perform `operation`
986
- # @return [Fixnum] the length of the string stored in `destkey`
1083
+ # @return [Integer] the length of the string stored in `destkey`
987
1084
  def bitop(operation, destkey, *keys)
988
1085
  synchronize do |client|
989
- client.call([:bitop, operation, destkey] + keys)
1086
+ client.call([:bitop, operation, destkey, *keys])
990
1087
  end
991
1088
  end
992
1089
 
993
1090
  # Return the position of the first bit set to 1 or 0 in a string.
994
1091
  #
995
1092
  # @param [String] key
996
- # @param [Fixnum] bit whether to look for the first 1 or 0 bit
997
- # @param [Fixnum] start start index
998
- # @param [Fixnum] stop stop index
999
- # @return [Fixnum] the position of the first 1/0 bit.
1093
+ # @param [Integer] bit whether to look for the first 1 or 0 bit
1094
+ # @param [Integer] start start index
1095
+ # @param [Integer] stop stop index
1096
+ # @return [Integer] the position of the first 1/0 bit.
1000
1097
  # -1 if looking for 1 and it is not found or start and stop are given.
1001
- def bitpos(key, bit, start=nil, stop=nil)
1002
- if stop and not start
1003
- raise(ArgumentError, 'stop parameter specified without start parameter')
1004
- end
1098
+ def bitpos(key, bit, start = nil, stop = nil)
1099
+ raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
1005
1100
 
1006
1101
  synchronize do |client|
1007
1102
  command = [:bitpos, key, bit]
@@ -1023,10 +1118,49 @@ class Redis
1023
1118
  end
1024
1119
  end
1025
1120
 
1121
+ # Get the value of key and delete the key. This command is similar to GET,
1122
+ # except for the fact that it also deletes the key on success.
1123
+ #
1124
+ # @param [String] key
1125
+ # @return [String] the old value stored in the key, or `nil` if the key
1126
+ # did not exist
1127
+ def getdel(key)
1128
+ synchronize do |client|
1129
+ client.call([:getdel, key])
1130
+ end
1131
+ end
1132
+
1133
+ # Get the value of key and optionally set its expiration. GETEX is similar to
1134
+ # GET, but is a write command with additional options. When no options are
1135
+ # provided, GETEX behaves like GET.
1136
+ #
1137
+ # @param [String] key
1138
+ # @param [Hash] options
1139
+ # - `:ex => Integer`: Set the specified expire time, in seconds.
1140
+ # - `:px => Integer`: Set the specified expire time, in milliseconds.
1141
+ # - `:exat => true`: Set the specified Unix time at which the key will
1142
+ # expire, in seconds.
1143
+ # - `:pxat => true`: Set the specified Unix time at which the key will
1144
+ # expire, in milliseconds.
1145
+ # - `:persist => true`: Remove the time to live associated with the key.
1146
+ # @return [String] The value of key, or nil when key does not exist.
1147
+ def getex(key, ex: nil, px: nil, exat: nil, pxat: nil, persist: false)
1148
+ args = [:getex, key]
1149
+ args << "EX" << ex if ex
1150
+ args << "PX" << px if px
1151
+ args << "EXAT" << exat if exat
1152
+ args << "PXAT" << pxat if pxat
1153
+ args << "PERSIST" if persist
1154
+
1155
+ synchronize do |client|
1156
+ client.call(args)
1157
+ end
1158
+ end
1159
+
1026
1160
  # Get the length of the value stored in a key.
1027
1161
  #
1028
1162
  # @param [String] key
1029
- # @return [Fixnum] the length of the value stored in the key, or 0
1163
+ # @return [Integer] the length of the value stored in the key, or 0
1030
1164
  # if the key does not exist
1031
1165
  def strlen(key)
1032
1166
  synchronize do |client|
@@ -1037,18 +1171,71 @@ class Redis
1037
1171
  # Get the length of a list.
1038
1172
  #
1039
1173
  # @param [String] key
1040
- # @return [Fixnum]
1174
+ # @return [Integer]
1041
1175
  def llen(key)
1042
1176
  synchronize do |client|
1043
1177
  client.call([:llen, key])
1044
1178
  end
1045
1179
  end
1046
1180
 
1181
+ # Remove the first/last element in a list, append/prepend it to another list and return it.
1182
+ #
1183
+ # @param [String] source source key
1184
+ # @param [String] destination destination key
1185
+ # @param [String, Symbol] where_source from where to remove the element from the source list
1186
+ # e.g. 'LEFT' - from head, 'RIGHT' - from tail
1187
+ # @param [String, Symbol] where_destination where to push the element to the source list
1188
+ # e.g. 'LEFT' - to head, 'RIGHT' - to tail
1189
+ #
1190
+ # @return [nil, String] the element, or nil when the source key does not exist
1191
+ #
1192
+ # @note This command comes in place of the now deprecated RPOPLPUSH.
1193
+ # Doing LMOVE RIGHT LEFT is equivalent.
1194
+ def lmove(source, destination, where_source, where_destination)
1195
+ where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
1196
+
1197
+ synchronize do |client|
1198
+ client.call([:lmove, source, destination, where_source, where_destination])
1199
+ end
1200
+ end
1201
+
1202
+ # Remove the first/last element in a list and append/prepend it
1203
+ # to another list and return it, or block until one is available.
1204
+ #
1205
+ # @example With timeout
1206
+ # element = redis.blmove("foo", "bar", "LEFT", "RIGHT", timeout: 5)
1207
+ # # => nil on timeout
1208
+ # # => "element" on success
1209
+ # @example Without timeout
1210
+ # element = redis.blmove("foo", "bar", "LEFT", "RIGHT")
1211
+ # # => "element"
1212
+ #
1213
+ # @param [String] source source key
1214
+ # @param [String] destination destination key
1215
+ # @param [String, Symbol] where_source from where to remove the element from the source list
1216
+ # e.g. 'LEFT' - from head, 'RIGHT' - from tail
1217
+ # @param [String, Symbol] where_destination where to push the element to the source list
1218
+ # e.g. 'LEFT' - to head, 'RIGHT' - to tail
1219
+ # @param [Hash] options
1220
+ # - `:timeout => Numeric`: timeout in seconds, defaults to no timeout
1221
+ #
1222
+ # @return [nil, String] the element, or nil when the source key does not exist or the timeout expired
1223
+ #
1224
+ def blmove(source, destination, where_source, where_destination, timeout: 0)
1225
+ where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
1226
+
1227
+ synchronize do |client|
1228
+ command = [:blmove, source, destination, where_source, where_destination, timeout]
1229
+ timeout += client.timeout if timeout > 0
1230
+ client.call_with_timeout(command, timeout)
1231
+ end
1232
+ end
1233
+
1047
1234
  # Prepend one or more values to a list, creating the list if it doesn't exist
1048
1235
  #
1049
1236
  # @param [String] key
1050
- # @param [String, Array] value string value, or array of string values to push
1051
- # @return [Fixnum] the length of the list after the push operation
1237
+ # @param [String, Array<String>] value string value, or array of string values to push
1238
+ # @return [Integer] the length of the list after the push operation
1052
1239
  def lpush(key, value)
1053
1240
  synchronize do |client|
1054
1241
  client.call([:lpush, key, value])
@@ -1059,7 +1246,7 @@ class Redis
1059
1246
  #
1060
1247
  # @param [String] key
1061
1248
  # @param [String] value
1062
- # @return [Fixnum] the length of the list after the push operation
1249
+ # @return [Integer] the length of the list after the push operation
1063
1250
  def lpushx(key, value)
1064
1251
  synchronize do |client|
1065
1252
  client.call([:lpushx, key, value])
@@ -1069,8 +1256,8 @@ class Redis
1069
1256
  # Append one or more values to a list, creating the list if it doesn't exist
1070
1257
  #
1071
1258
  # @param [String] key
1072
- # @param [String] value
1073
- # @return [Fixnum] the length of the list after the push operation
1259
+ # @param [String, Array<String>] value string value, or array of string values to push
1260
+ # @return [Integer] the length of the list after the push operation
1074
1261
  def rpush(key, value)
1075
1262
  synchronize do |client|
1076
1263
  client.call([:rpush, key, value])
@@ -1081,30 +1268,36 @@ class Redis
1081
1268
  #
1082
1269
  # @param [String] key
1083
1270
  # @param [String] value
1084
- # @return [Fixnum] the length of the list after the push operation
1271
+ # @return [Integer] the length of the list after the push operation
1085
1272
  def rpushx(key, value)
1086
1273
  synchronize do |client|
1087
1274
  client.call([:rpushx, key, value])
1088
1275
  end
1089
1276
  end
1090
1277
 
1091
- # Remove and get the first element in a list.
1278
+ # Remove and get the first elements in a list.
1092
1279
  #
1093
1280
  # @param [String] key
1094
- # @return [String]
1095
- def lpop(key)
1281
+ # @param [Integer] count number of elements to remove
1282
+ # @return [String, Array<String>] the values of the first elements
1283
+ def lpop(key, count = nil)
1096
1284
  synchronize do |client|
1097
- client.call([:lpop, key])
1285
+ command = [:lpop, key]
1286
+ command << count if count
1287
+ client.call(command)
1098
1288
  end
1099
1289
  end
1100
1290
 
1101
- # Remove and get the last element in a list.
1291
+ # Remove and get the last elements in a list.
1102
1292
  #
1103
1293
  # @param [String] key
1104
- # @return [String]
1105
- def rpop(key)
1294
+ # @param [Integer] count number of elements to remove
1295
+ # @return [String, Array<String>] the values of the last elements
1296
+ def rpop(key, count = nil)
1106
1297
  synchronize do |client|
1107
- client.call([:rpop, key])
1298
+ command = [:rpop, key]
1299
+ command << count if count
1300
+ client.call(command)
1108
1301
  end
1109
1302
  end
1110
1303
 
@@ -1119,28 +1312,27 @@ class Redis
1119
1312
  end
1120
1313
  end
1121
1314
 
1122
- def _bpop(cmd, args)
1123
- options = {}
1124
-
1125
- case args.last
1126
- when Hash
1315
+ def _bpop(cmd, args, &blk)
1316
+ timeout = if args.last.is_a?(Hash)
1127
1317
  options = args.pop
1128
- when Integer
1318
+ options[:timeout]
1319
+ elsif args.last.respond_to?(:to_int)
1129
1320
  # Issue deprecation notice in obnoxious mode...
1130
- options[:timeout] = args.pop
1321
+ args.pop.to_int
1131
1322
  end
1132
1323
 
1324
+ timeout ||= 0
1325
+
1133
1326
  if args.size > 1
1134
1327
  # Issue deprecation notice in obnoxious mode...
1135
1328
  end
1136
1329
 
1137
1330
  keys = args.flatten
1138
- timeout = options[:timeout] || 0
1139
1331
 
1140
1332
  synchronize do |client|
1141
1333
  command = [cmd, keys, timeout]
1142
1334
  timeout += client.timeout if timeout > 0
1143
- client.call_with_timeout(command, timeout)
1335
+ client.call_with_timeout(command, timeout, &blk)
1144
1336
  end
1145
1337
  end
1146
1338
 
@@ -1160,7 +1352,7 @@ class Redis
1160
1352
  # @param [String, Array<String>] keys one or more keys to perform the
1161
1353
  # blocking pop on
1162
1354
  # @param [Hash] options
1163
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1355
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1164
1356
  #
1165
1357
  # @return [nil, [String, String]]
1166
1358
  # - `nil` when the operation timed out
@@ -1174,7 +1366,7 @@ class Redis
1174
1366
  # @param [String, Array<String>] keys one or more keys to perform the
1175
1367
  # blocking pop on
1176
1368
  # @param [Hash] options
1177
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1369
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1178
1370
  #
1179
1371
  # @return [nil, [String, String]]
1180
1372
  # - `nil` when the operation timed out
@@ -1191,20 +1383,12 @@ class Redis
1191
1383
  # @param [String] source source key
1192
1384
  # @param [String] destination destination key
1193
1385
  # @param [Hash] options
1194
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1386
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1195
1387
  #
1196
1388
  # @return [nil, String]
1197
1389
  # - `nil` when the operation timed out
1198
1390
  # - the element was popped and pushed otherwise
1199
- def brpoplpush(source, destination, options = {})
1200
- case options
1201
- when Integer
1202
- # Issue deprecation notice in obnoxious mode...
1203
- options = { :timeout => options }
1204
- end
1205
-
1206
- timeout = options[:timeout] || 0
1207
-
1391
+ def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
1208
1392
  synchronize do |client|
1209
1393
  command = [:brpoplpush, source, destination, timeout]
1210
1394
  timeout += client.timeout if timeout > 0
@@ -1215,7 +1399,7 @@ class Redis
1215
1399
  # Get an element from a list by its index.
1216
1400
  #
1217
1401
  # @param [String] key
1218
- # @param [Fixnum] index
1402
+ # @param [Integer] index
1219
1403
  # @return [String]
1220
1404
  def lindex(key, index)
1221
1405
  synchronize do |client|
@@ -1229,7 +1413,7 @@ class Redis
1229
1413
  # @param [String, Symbol] where `BEFORE` or `AFTER`
1230
1414
  # @param [String] pivot reference element
1231
1415
  # @param [String] value
1232
- # @return [Fixnum] length of the list after the insert operation, or `-1`
1416
+ # @return [Integer] length of the list after the insert operation, or `-1`
1233
1417
  # when the element `pivot` was not found
1234
1418
  def linsert(key, where, pivot, value)
1235
1419
  synchronize do |client|
@@ -1240,8 +1424,8 @@ class Redis
1240
1424
  # Get a range of elements from a list.
1241
1425
  #
1242
1426
  # @param [String] key
1243
- # @param [Fixnum] start start index
1244
- # @param [Fixnum] stop stop index
1427
+ # @param [Integer] start start index
1428
+ # @param [Integer] stop stop index
1245
1429
  # @return [Array<String>]
1246
1430
  def lrange(key, start, stop)
1247
1431
  synchronize do |client|
@@ -1252,12 +1436,12 @@ class Redis
1252
1436
  # Remove elements from a list.
1253
1437
  #
1254
1438
  # @param [String] key
1255
- # @param [Fixnum] count number of elements to remove. Use a positive
1439
+ # @param [Integer] count number of elements to remove. Use a positive
1256
1440
  # value to remove the first `count` occurrences of `value`. A negative
1257
1441
  # value to remove the last `count` occurrences of `value`. Or zero, to
1258
1442
  # remove all occurrences of `value` from the list.
1259
1443
  # @param [String] value
1260
- # @return [Fixnum] the number of removed elements
1444
+ # @return [Integer] the number of removed elements
1261
1445
  def lrem(key, count, value)
1262
1446
  synchronize do |client|
1263
1447
  client.call([:lrem, key, count, value])
@@ -1267,7 +1451,7 @@ class Redis
1267
1451
  # Set the value of an element in a list by its index.
1268
1452
  #
1269
1453
  # @param [String] key
1270
- # @param [Fixnum] index
1454
+ # @param [Integer] index
1271
1455
  # @param [String] value
1272
1456
  # @return [String] `OK`
1273
1457
  def lset(key, index, value)
@@ -1279,8 +1463,8 @@ class Redis
1279
1463
  # Trim a list to the specified range.
1280
1464
  #
1281
1465
  # @param [String] key
1282
- # @param [Fixnum] start start index
1283
- # @param [Fixnum] stop stop index
1466
+ # @param [Integer] start start index
1467
+ # @param [Integer] stop stop index
1284
1468
  # @return [String] `OK`
1285
1469
  def ltrim(key, start, stop)
1286
1470
  synchronize do |client|
@@ -1291,7 +1475,7 @@ class Redis
1291
1475
  # Get the number of members in a set.
1292
1476
  #
1293
1477
  # @param [String] key
1294
- # @return [Fixnum]
1478
+ # @return [Integer]
1295
1479
  def scard(key)
1296
1480
  synchronize do |client|
1297
1481
  client.call([:scard, key])
@@ -1302,8 +1486,8 @@ class Redis
1302
1486
  #
1303
1487
  # @param [String] key
1304
1488
  # @param [String, Array<String>] member one member, or array of members
1305
- # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1306
- # holding whether or not adding the member succeeded, or `Fixnum` when an
1489
+ # @return [Boolean, Integer] `Boolean` when a single member is specified,
1490
+ # holding whether or not adding the member succeeded, or `Integer` when an
1307
1491
  # array of members is specified, holding the number of members that were
1308
1492
  # successfully added
1309
1493
  def sadd(key, member)
@@ -1324,8 +1508,8 @@ class Redis
1324
1508
  #
1325
1509
  # @param [String] key
1326
1510
  # @param [String, Array<String>] member one member, or array of members
1327
- # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1328
- # holding whether or not removing the member succeeded, or `Fixnum` when an
1511
+ # @return [Boolean, Integer] `Boolean` when a single member is specified,
1512
+ # holding whether or not removing the member succeeded, or `Integer` when an
1329
1513
  # array of members is specified, holding the number of members that were
1330
1514
  # successfully removed
1331
1515
  def srem(key, member)
@@ -1346,7 +1530,7 @@ class Redis
1346
1530
  #
1347
1531
  # @param [String] key
1348
1532
  # @return [String]
1349
- # @param [Fixnum] count
1533
+ # @param [Integer] count
1350
1534
  def spop(key, count = nil)
1351
1535
  synchronize do |client|
1352
1536
  if count.nil?
@@ -1360,7 +1544,7 @@ class Redis
1360
1544
  # Get one or more random members from a set.
1361
1545
  #
1362
1546
  # @param [String] key
1363
- # @param [Fixnum] count
1547
+ # @param [Integer] count
1364
1548
  # @return [String]
1365
1549
  def srandmember(key, count = nil)
1366
1550
  synchronize do |client|
@@ -1395,6 +1579,19 @@ class Redis
1395
1579
  end
1396
1580
  end
1397
1581
 
1582
+ # Determine if multiple values are members of a set.
1583
+ #
1584
+ # @param [String] key
1585
+ # @param [String, Array<String>] members
1586
+ # @return [Array<Boolean>]
1587
+ def smismember(key, *members)
1588
+ synchronize do |client|
1589
+ client.call([:smismember, key, *members]) do |reply|
1590
+ reply.map(&Boolify)
1591
+ end
1592
+ end
1593
+ end
1594
+
1398
1595
  # Get all the members in a set.
1399
1596
  #
1400
1597
  # @param [String] key
@@ -1411,7 +1608,7 @@ class Redis
1411
1608
  # @return [Array<String>] members in the difference
1412
1609
  def sdiff(*keys)
1413
1610
  synchronize do |client|
1414
- client.call([:sdiff] + keys)
1611
+ client.call([:sdiff, *keys])
1415
1612
  end
1416
1613
  end
1417
1614
 
@@ -1419,10 +1616,10 @@ class Redis
1419
1616
  #
1420
1617
  # @param [String] destination destination key
1421
1618
  # @param [String, Array<String>] keys keys pointing to sets to subtract
1422
- # @return [Fixnum] number of elements in the resulting set
1619
+ # @return [Integer] number of elements in the resulting set
1423
1620
  def sdiffstore(destination, *keys)
1424
1621
  synchronize do |client|
1425
- client.call([:sdiffstore, destination] + keys)
1622
+ client.call([:sdiffstore, destination, *keys])
1426
1623
  end
1427
1624
  end
1428
1625
 
@@ -1432,7 +1629,7 @@ class Redis
1432
1629
  # @return [Array<String>] members in the intersection
1433
1630
  def sinter(*keys)
1434
1631
  synchronize do |client|
1435
- client.call([:sinter] + keys)
1632
+ client.call([:sinter, *keys])
1436
1633
  end
1437
1634
  end
1438
1635
 
@@ -1440,10 +1637,10 @@ class Redis
1440
1637
  #
1441
1638
  # @param [String] destination destination key
1442
1639
  # @param [String, Array<String>] keys keys pointing to sets to intersect
1443
- # @return [Fixnum] number of elements in the resulting set
1640
+ # @return [Integer] number of elements in the resulting set
1444
1641
  def sinterstore(destination, *keys)
1445
1642
  synchronize do |client|
1446
- client.call([:sinterstore, destination] + keys)
1643
+ client.call([:sinterstore, destination, *keys])
1447
1644
  end
1448
1645
  end
1449
1646
 
@@ -1453,7 +1650,7 @@ class Redis
1453
1650
  # @return [Array<String>] members in the union
1454
1651
  def sunion(*keys)
1455
1652
  synchronize do |client|
1456
- client.call([:sunion] + keys)
1653
+ client.call([:sunion, *keys])
1457
1654
  end
1458
1655
  end
1459
1656
 
@@ -1461,10 +1658,10 @@ class Redis
1461
1658
  #
1462
1659
  # @param [String] destination destination key
1463
1660
  # @param [String, Array<String>] keys keys pointing to sets to unify
1464
- # @return [Fixnum] number of elements in the resulting set
1661
+ # @return [Integer] number of elements in the resulting set
1465
1662
  def sunionstore(destination, *keys)
1466
1663
  synchronize do |client|
1467
- client.call([:sunionstore, destination] + keys)
1664
+ client.call([:sunionstore, destination, *keys])
1468
1665
  end
1469
1666
  end
1470
1667
 
@@ -1475,7 +1672,7 @@ class Redis
1475
1672
  # # => 4
1476
1673
  #
1477
1674
  # @param [String] key
1478
- # @return [Fixnum]
1675
+ # @return [Integer]
1479
1676
  def zcard(key)
1480
1677
  synchronize do |client|
1481
1678
  client.call([:zcard, key])
@@ -1499,6 +1696,10 @@ class Redis
1499
1696
  # add elements)
1500
1697
  # - `:nx => true`: Don't update already existing elements (always
1501
1698
  # add new elements)
1699
+ # - `:lt => true`: Only update existing elements if the new score
1700
+ # is less than the current score
1701
+ # - `:gt => true`: Only update existing elements if the new score
1702
+ # is greater than the current score
1502
1703
  # - `:ch => true`: Modify the return value from the number of new
1503
1704
  # elements added, to the total number of elements changed (CH is an
1504
1705
  # abbreviation of changed); changed elements are new elements added
@@ -1506,38 +1707,29 @@ class Redis
1506
1707
  # - `:incr => true`: When this option is specified ZADD acts like
1507
1708
  # ZINCRBY; only one score-element pair can be specified in this mode
1508
1709
  #
1509
- # @return [Boolean, Fixnum, Float]
1710
+ # @return [Boolean, Integer, Float]
1510
1711
  # - `Boolean` when a single pair is specified, holding whether or not it was
1511
1712
  # **added** to the sorted set.
1512
- # - `Fixnum` when an array of pairs is specified, holding the number of
1713
+ # - `Integer` when an array of pairs is specified, holding the number of
1513
1714
  # pairs that were **added** to the sorted set.
1514
1715
  # - `Float` when option :incr is specified, holding the score of the member
1515
1716
  # after incrementing it.
1516
- def zadd(key, *args) #, options
1517
- zadd_options = []
1518
- if args.last.is_a?(Hash)
1519
- options = args.pop
1520
-
1521
- nx = options[:nx]
1522
- zadd_options << "NX" if nx
1523
-
1524
- xx = options[:xx]
1525
- zadd_options << "XX" if xx
1526
-
1527
- ch = options[:ch]
1528
- zadd_options << "CH" if ch
1529
-
1530
- incr = options[:incr]
1531
- zadd_options << "INCR" if incr
1532
- end
1717
+ def zadd(key, *args, nx: nil, xx: nil, lt: nil, gt: nil, ch: nil, incr: nil)
1718
+ command = [:zadd, key]
1719
+ command << "NX" if nx
1720
+ command << "XX" if xx
1721
+ command << "LT" if lt
1722
+ command << "GT" if gt
1723
+ command << "CH" if ch
1724
+ command << "INCR" if incr
1533
1725
 
1534
1726
  synchronize do |client|
1535
1727
  if args.size == 1 && args[0].is_a?(Array)
1536
1728
  # Variadic: return float if INCR, integer if !INCR
1537
- client.call([:zadd, key] + zadd_options + args[0], &(incr ? Floatify : nil))
1729
+ client.call(command + args[0], &(incr ? Floatify : nil))
1538
1730
  elsif args.size == 2
1539
1731
  # Single pair: return float if INCR, boolean if !INCR
1540
- client.call([:zadd, key] + zadd_options + args, &(incr ? Floatify : Boolify))
1732
+ client.call(command + args, &(incr ? Floatify : Boolify))
1541
1733
  else
1542
1734
  raise ArgumentError, "wrong number of arguments"
1543
1735
  end
@@ -1572,10 +1764,10 @@ class Redis
1572
1764
  # - a single member
1573
1765
  # - an array of members
1574
1766
  #
1575
- # @return [Boolean, Fixnum]
1767
+ # @return [Boolean, Integer]
1576
1768
  # - `Boolean` when a single member is specified, holding whether or not it
1577
1769
  # was removed from the sorted set
1578
- # - `Fixnum` when an array of pairs is specified, holding the number of
1770
+ # - `Integer` when an array of pairs is specified, holding the number of
1579
1771
  # members that were removed to the sorted set
1580
1772
  def zrem(key, member)
1581
1773
  synchronize do |client|
@@ -1591,6 +1783,90 @@ class Redis
1591
1783
  end
1592
1784
  end
1593
1785
 
1786
+ # Removes and returns up to count members with the highest scores in the sorted set stored at key.
1787
+ #
1788
+ # @example Popping a member
1789
+ # redis.zpopmax('zset')
1790
+ # #=> ['b', 2.0]
1791
+ # @example With count option
1792
+ # redis.zpopmax('zset', 2)
1793
+ # #=> [['b', 2.0], ['a', 1.0]]
1794
+ #
1795
+ # @params key [String] a key of the sorted set
1796
+ # @params count [Integer] a number of members
1797
+ #
1798
+ # @return [Array<String, Float>] element and score pair if count is not specified
1799
+ # @return [Array<Array<String, Float>>] list of popped elements and scores
1800
+ def zpopmax(key, count = nil)
1801
+ synchronize do |client|
1802
+ members = client.call([:zpopmax, key, count].compact, &FloatifyPairs)
1803
+ count.to_i > 1 ? members : members.first
1804
+ end
1805
+ end
1806
+
1807
+ # Removes and returns up to count members with the lowest scores in the sorted set stored at key.
1808
+ #
1809
+ # @example Popping a member
1810
+ # redis.zpopmin('zset')
1811
+ # #=> ['a', 1.0]
1812
+ # @example With count option
1813
+ # redis.zpopmin('zset', 2)
1814
+ # #=> [['a', 1.0], ['b', 2.0]]
1815
+ #
1816
+ # @params key [String] a key of the sorted set
1817
+ # @params count [Integer] a number of members
1818
+ #
1819
+ # @return [Array<String, Float>] element and score pair if count is not specified
1820
+ # @return [Array<Array<String, Float>>] list of popped elements and scores
1821
+ def zpopmin(key, count = nil)
1822
+ synchronize do |client|
1823
+ members = client.call([:zpopmin, key, count].compact, &FloatifyPairs)
1824
+ count.to_i > 1 ? members : members.first
1825
+ end
1826
+ end
1827
+
1828
+ # Removes and returns up to count members with the highest scores in the sorted set stored at keys,
1829
+ # or block until one is available.
1830
+ #
1831
+ # @example Popping a member from a sorted set
1832
+ # redis.bzpopmax('zset', 1)
1833
+ # #=> ['zset', 'b', 2.0]
1834
+ # @example Popping a member from multiple sorted sets
1835
+ # redis.bzpopmax('zset1', 'zset2', 1)
1836
+ # #=> ['zset1', 'b', 2.0]
1837
+ #
1838
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
1839
+ # @params timeout [Integer] the maximum number of seconds to block
1840
+ #
1841
+ # @return [Array<String, String, Float>] a touple of key, member and score
1842
+ # @return [nil] when no element could be popped and the timeout expired
1843
+ def bzpopmax(*args)
1844
+ _bpop(:bzpopmax, args) do |reply|
1845
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
1846
+ end
1847
+ end
1848
+
1849
+ # Removes and returns up to count members with the lowest scores in the sorted set stored at keys,
1850
+ # or block until one is available.
1851
+ #
1852
+ # @example Popping a member from a sorted set
1853
+ # redis.bzpopmin('zset', 1)
1854
+ # #=> ['zset', 'a', 1.0]
1855
+ # @example Popping a member from multiple sorted sets
1856
+ # redis.bzpopmin('zset1', 'zset2', 1)
1857
+ # #=> ['zset1', 'a', 1.0]
1858
+ #
1859
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
1860
+ # @params timeout [Integer] the maximum number of seconds to block
1861
+ #
1862
+ # @return [Array<String, String, Float>] a touple of key, member and score
1863
+ # @return [nil] when no element could be popped and the timeout expired
1864
+ def bzpopmin(*args)
1865
+ _bpop(:bzpopmin, args) do |reply|
1866
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
1867
+ end
1868
+ end
1869
+
1594
1870
  # Get the score associated with the given member in a sorted set.
1595
1871
  #
1596
1872
  # @example Get the score for member "a"
@@ -1606,6 +1882,63 @@ class Redis
1606
1882
  end
1607
1883
  end
1608
1884
 
1885
+ # Get the scores associated with the given members in a sorted set.
1886
+ #
1887
+ # @example Get the scores for members "a" and "b"
1888
+ # redis.zmscore("zset", "a", "b")
1889
+ # # => [32.0, 48.0]
1890
+ #
1891
+ # @param [String] key
1892
+ # @param [String, Array<String>] members
1893
+ # @return [Array<Float>] scores of the members
1894
+ def zmscore(key, *members)
1895
+ synchronize do |client|
1896
+ client.call([:zmscore, key, *members]) do |reply|
1897
+ reply.map(&Floatify)
1898
+ end
1899
+ end
1900
+ end
1901
+
1902
+ # Get one or more random members from a sorted set.
1903
+ #
1904
+ # @example Get one random member
1905
+ # redis.zrandmember("zset")
1906
+ # # => "a"
1907
+ # @example Get multiple random members
1908
+ # redis.zrandmember("zset", 2)
1909
+ # # => ["a", "b"]
1910
+ # @example Gem multiple random members with scores
1911
+ # redis.zrandmember("zset", 2, with_scores: true)
1912
+ # # => [["a", 2.0], ["b", 3.0]]
1913
+ #
1914
+ # @param [String] key
1915
+ # @param [Integer] count
1916
+ # @param [Hash] options
1917
+ # - `:with_scores => true`: include scores in output
1918
+ #
1919
+ # @return [nil, String, Array<String>, Array<[String, Float]>]
1920
+ # - when `key` does not exist or set is empty, `nil`
1921
+ # - when `count` is not specified, a member
1922
+ # - when `count` is specified and `:with_scores` is not specified, an array of members
1923
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
1924
+ def zrandmember(key, count = nil, withscores: false, with_scores: withscores)
1925
+ if with_scores && count.nil?
1926
+ raise ArgumentError, "count argument must be specified"
1927
+ end
1928
+
1929
+ args = [:zrandmember, key]
1930
+ args << count if count
1931
+
1932
+ if with_scores
1933
+ args << "WITHSCORES"
1934
+ block = FloatifyPairs
1935
+ end
1936
+
1937
+ synchronize do |client|
1938
+ client.call(args, &block)
1939
+ end
1940
+ end
1941
+
1609
1942
  # Return a range of members in a sorted set, by index.
1610
1943
  #
1611
1944
  # @example Retrieve all members from a sorted set
@@ -1616,18 +1949,16 @@ class Redis
1616
1949
  # # => [["a", 32.0], ["b", 64.0]]
1617
1950
  #
1618
1951
  # @param [String] key
1619
- # @param [Fixnum] start start index
1620
- # @param [Fixnum] stop stop index
1952
+ # @param [Integer] start start index
1953
+ # @param [Integer] stop stop index
1621
1954
  # @param [Hash] options
1622
1955
  # - `:with_scores => true`: include scores in output
1623
1956
  #
1624
1957
  # @return [Array<String>, Array<[String, Float]>]
1625
1958
  # - when `:with_scores` is not specified, an array of members
1626
1959
  # - when `:with_scores` is specified, an array with `[member, score]` pairs
1627
- def zrange(key, start, stop, options = {})
1628
- args = []
1629
-
1630
- with_scores = options[:with_scores] || options[:withscores]
1960
+ def zrange(key, start, stop, withscores: false, with_scores: withscores)
1961
+ args = [:zrange, key, start, stop]
1631
1962
 
1632
1963
  if with_scores
1633
1964
  args << "WITHSCORES"
@@ -1635,7 +1966,7 @@ class Redis
1635
1966
  end
1636
1967
 
1637
1968
  synchronize do |client|
1638
- client.call([:zrange, key, start, stop] + args, &block)
1969
+ client.call(args, &block)
1639
1970
  end
1640
1971
  end
1641
1972
 
@@ -1650,10 +1981,8 @@ class Redis
1650
1981
  # # => [["b", 64.0], ["a", 32.0]]
1651
1982
  #
1652
1983
  # @see #zrange
1653
- def zrevrange(key, start, stop, options = {})
1654
- args = []
1655
-
1656
- with_scores = options[:with_scores] || options[:withscores]
1984
+ def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
1985
+ args = [:zrevrange, key, start, stop]
1657
1986
 
1658
1987
  if with_scores
1659
1988
  args << "WITHSCORES"
@@ -1661,7 +1990,7 @@ class Redis
1661
1990
  end
1662
1991
 
1663
1992
  synchronize do |client|
1664
- client.call([:zrevrange, key, start, stop] + args, &block)
1993
+ client.call(args, &block)
1665
1994
  end
1666
1995
  end
1667
1996
 
@@ -1669,7 +1998,7 @@ class Redis
1669
1998
  #
1670
1999
  # @param [String] key
1671
2000
  # @param [String] member
1672
- # @return [Fixnum]
2001
+ # @return [Integer]
1673
2002
  def zrank(key, member)
1674
2003
  synchronize do |client|
1675
2004
  client.call([:zrank, key, member])
@@ -1681,7 +2010,7 @@ class Redis
1681
2010
  #
1682
2011
  # @param [String] key
1683
2012
  # @param [String] member
1684
- # @return [Fixnum]
2013
+ # @return [Integer]
1685
2014
  def zrevrank(key, member)
1686
2015
  synchronize do |client|
1687
2016
  client.call([:zrevrank, key, member])
@@ -1698,15 +2027,39 @@ class Redis
1698
2027
  # # => 5
1699
2028
  #
1700
2029
  # @param [String] key
1701
- # @param [Fixnum] start start index
1702
- # @param [Fixnum] stop stop index
1703
- # @return [Fixnum] number of members that were removed
2030
+ # @param [Integer] start start index
2031
+ # @param [Integer] stop stop index
2032
+ # @return [Integer] number of members that were removed
1704
2033
  def zremrangebyrank(key, start, stop)
1705
2034
  synchronize do |client|
1706
2035
  client.call([:zremrangebyrank, key, start, stop])
1707
2036
  end
1708
2037
  end
1709
2038
 
2039
+ # Count the members, with the same score in a sorted set, within the given lexicographical range.
2040
+ #
2041
+ # @example Count members matching a
2042
+ # redis.zlexcount("zset", "[a", "[a\xff")
2043
+ # # => 1
2044
+ # @example Count members matching a-z
2045
+ # redis.zlexcount("zset", "[a", "[z\xff")
2046
+ # # => 26
2047
+ #
2048
+ # @param [String] key
2049
+ # @param [String] min
2050
+ # - inclusive minimum is specified by prefixing `(`
2051
+ # - exclusive minimum is specified by prefixing `[`
2052
+ # @param [String] max
2053
+ # - inclusive maximum is specified by prefixing `(`
2054
+ # - exclusive maximum is specified by prefixing `[`
2055
+ #
2056
+ # @return [Integer] number of members within the specified lexicographical range
2057
+ def zlexcount(key, min, max)
2058
+ synchronize do |client|
2059
+ client.call([:zlexcount, key, min, max])
2060
+ end
2061
+ end
2062
+
1710
2063
  # Return a range of members with the same score in a sorted set, by lexicographical ordering
1711
2064
  #
1712
2065
  # @example Retrieve members matching a
@@ -1728,14 +2081,16 @@ class Redis
1728
2081
  # `count` members
1729
2082
  #
1730
2083
  # @return [Array<String>, Array<[String, Float]>]
1731
- def zrangebylex(key, min, max, options = {})
1732
- args = []
2084
+ def zrangebylex(key, min, max, limit: nil)
2085
+ args = [:zrangebylex, key, min, max]
1733
2086
 
1734
- limit = options[:limit]
1735
- args.concat(["LIMIT"] + limit) if limit
2087
+ if limit
2088
+ args << "LIMIT"
2089
+ args.concat(limit)
2090
+ end
1736
2091
 
1737
2092
  synchronize do |client|
1738
- client.call([:zrangebylex, key, min, max] + args)
2093
+ client.call(args)
1739
2094
  end
1740
2095
  end
1741
2096
 
@@ -1750,14 +2105,16 @@ class Redis
1750
2105
  # # => ["abbygail", "abby"]
1751
2106
  #
1752
2107
  # @see #zrangebylex
1753
- def zrevrangebylex(key, max, min, options = {})
1754
- args = []
2108
+ def zrevrangebylex(key, max, min, limit: nil)
2109
+ args = [:zrevrangebylex, key, max, min]
1755
2110
 
1756
- limit = options[:limit]
1757
- args.concat(["LIMIT"] + limit) if limit
2111
+ if limit
2112
+ args << "LIMIT"
2113
+ args.concat(limit)
2114
+ end
1758
2115
 
1759
2116
  synchronize do |client|
1760
- client.call([:zrevrangebylex, key, max, min] + args)
2117
+ client.call(args)
1761
2118
  end
1762
2119
  end
1763
2120
 
@@ -1788,21 +2145,21 @@ class Redis
1788
2145
  # @return [Array<String>, Array<[String, Float]>]
1789
2146
  # - when `:with_scores` is not specified, an array of members
1790
2147
  # - when `:with_scores` is specified, an array with `[member, score]` pairs
1791
- def zrangebyscore(key, min, max, options = {})
1792
- args = []
1793
-
1794
- with_scores = options[:with_scores] || options[:withscores]
2148
+ def zrangebyscore(key, min, max, withscores: false, with_scores: withscores, limit: nil)
2149
+ args = [:zrangebyscore, key, min, max]
1795
2150
 
1796
2151
  if with_scores
1797
2152
  args << "WITHSCORES"
1798
2153
  block = FloatifyPairs
1799
2154
  end
1800
2155
 
1801
- limit = options[:limit]
1802
- args.concat(["LIMIT"] + limit) if limit
2156
+ if limit
2157
+ args << "LIMIT"
2158
+ args.concat(limit)
2159
+ end
1803
2160
 
1804
2161
  synchronize do |client|
1805
- client.call([:zrangebyscore, key, min, max] + args, &block)
2162
+ client.call(args, &block)
1806
2163
  end
1807
2164
  end
1808
2165
 
@@ -1820,21 +2177,21 @@ class Redis
1820
2177
  # # => [["b", 64.0], ["a", 32.0]]
1821
2178
  #
1822
2179
  # @see #zrangebyscore
1823
- def zrevrangebyscore(key, max, min, options = {})
1824
- args = []
1825
-
1826
- with_scores = options[:with_scores] || options[:withscores]
2180
+ def zrevrangebyscore(key, max, min, withscores: false, with_scores: withscores, limit: nil)
2181
+ args = [:zrevrangebyscore, key, max, min]
1827
2182
 
1828
2183
  if with_scores
1829
- args << ["WITHSCORES"]
2184
+ args << "WITHSCORES"
1830
2185
  block = FloatifyPairs
1831
2186
  end
1832
2187
 
1833
- limit = options[:limit]
1834
- args.concat(["LIMIT"] + limit) if limit
2188
+ if limit
2189
+ args << "LIMIT"
2190
+ args.concat(limit)
2191
+ end
1835
2192
 
1836
2193
  synchronize do |client|
1837
- client.call([:zrevrangebyscore, key, max, min] + args, &block)
2194
+ client.call(args, &block)
1838
2195
  end
1839
2196
  end
1840
2197
 
@@ -1854,7 +2211,7 @@ class Redis
1854
2211
  # @param [String] max
1855
2212
  # - inclusive maximum score is specified verbatim
1856
2213
  # - exclusive maximum score is specified by prefixing `(`
1857
- # @return [Fixnum] number of members that were removed
2214
+ # @return [Integer] number of members that were removed
1858
2215
  def zremrangebyscore(key, min, max)
1859
2216
  synchronize do |client|
1860
2217
  client.call([:zremrangebyscore, key, min, max])
@@ -1877,13 +2234,52 @@ class Redis
1877
2234
  # @param [String] max
1878
2235
  # - inclusive maximum score is specified verbatim
1879
2236
  # - exclusive maximum score is specified by prefixing `(`
1880
- # @return [Fixnum] number of members in within the specified range
2237
+ # @return [Integer] number of members in within the specified range
1881
2238
  def zcount(key, min, max)
1882
2239
  synchronize do |client|
1883
2240
  client.call([:zcount, key, min, max])
1884
2241
  end
1885
2242
  end
1886
2243
 
2244
+ # Return the intersection of multiple sorted sets
2245
+ #
2246
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`
2247
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0])
2248
+ # # => ["v1", "v2"]
2249
+ # @example Retrieve the intersection of `2*zsetA` and `1*zsetB`, and their scores
2250
+ # redis.zinter("zsetA", "zsetB", :weights => [2.0, 1.0], :with_scores => true)
2251
+ # # => [["v1", 3.0], ["v2", 6.0]]
2252
+ #
2253
+ # @param [String, Array<String>] keys one or more keys to intersect
2254
+ # @param [Hash] options
2255
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
2256
+ # sorted sets
2257
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
2258
+ # - `:with_scores => true`: include scores in output
2259
+ #
2260
+ # @return [Array<String>, Array<[String, Float]>]
2261
+ # - when `:with_scores` is not specified, an array of members
2262
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
2263
+ def zinter(*keys, weights: nil, aggregate: nil, with_scores: false)
2264
+ args = [:zinter, keys.size, *keys]
2265
+
2266
+ if weights
2267
+ args << "WEIGHTS"
2268
+ args.concat(weights)
2269
+ end
2270
+
2271
+ args << "AGGREGATE" << aggregate if aggregate
2272
+
2273
+ if with_scores
2274
+ args << "WITHSCORES"
2275
+ block = FloatifyPairs
2276
+ end
2277
+
2278
+ synchronize do |client|
2279
+ client.call(args, &block)
2280
+ end
2281
+ end
2282
+
1887
2283
  # Intersect multiple sorted sets and store the resulting sorted set in a new
1888
2284
  # key.
1889
2285
  #
@@ -1897,18 +2293,19 @@ class Redis
1897
2293
  # - `:weights => [Float, Float, ...]`: weights to associate with source
1898
2294
  # sorted sets
1899
2295
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1900
- # @return [Fixnum] number of elements in the resulting sorted set
1901
- def zinterstore(destination, keys, options = {})
1902
- args = []
2296
+ # @return [Integer] number of elements in the resulting sorted set
2297
+ def zinterstore(destination, keys, weights: nil, aggregate: nil)
2298
+ args = [:zinterstore, destination, keys.size, *keys]
1903
2299
 
1904
- weights = options[:weights]
1905
- args.concat(["WEIGHTS"] + weights) if weights
2300
+ if weights
2301
+ args << "WEIGHTS"
2302
+ args.concat(weights)
2303
+ end
1906
2304
 
1907
- aggregate = options[:aggregate]
1908
- args.concat(["AGGREGATE", aggregate]) if aggregate
2305
+ args << "AGGREGATE" << aggregate if aggregate
1909
2306
 
1910
2307
  synchronize do |client|
1911
- client.call([:zinterstore, destination, keys.size] + keys + args)
2308
+ client.call(args)
1912
2309
  end
1913
2310
  end
1914
2311
 
@@ -1924,40 +2321,46 @@ class Redis
1924
2321
  # - `:weights => [Float, Float, ...]`: weights to associate with source
1925
2322
  # sorted sets
1926
2323
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1927
- # @return [Fixnum] number of elements in the resulting sorted set
1928
- def zunionstore(destination, keys, options = {})
1929
- args = []
2324
+ # @return [Integer] number of elements in the resulting sorted set
2325
+ def zunionstore(destination, keys, weights: nil, aggregate: nil)
2326
+ args = [:zunionstore, destination, keys.size, *keys]
1930
2327
 
1931
- weights = options[:weights]
1932
- args.concat(["WEIGHTS"] + weights) if weights
2328
+ if weights
2329
+ args << "WEIGHTS"
2330
+ args.concat(weights)
2331
+ end
1933
2332
 
1934
- aggregate = options[:aggregate]
1935
- args.concat(["AGGREGATE", aggregate]) if aggregate
2333
+ args << "AGGREGATE" << aggregate if aggregate
1936
2334
 
1937
2335
  synchronize do |client|
1938
- client.call([:zunionstore, destination, keys.size] + keys + args)
2336
+ client.call(args)
1939
2337
  end
1940
2338
  end
1941
2339
 
1942
2340
  # Get the number of fields in a hash.
1943
2341
  #
1944
2342
  # @param [String] key
1945
- # @return [Fixnum] number of fields in the hash
2343
+ # @return [Integer] number of fields in the hash
1946
2344
  def hlen(key)
1947
2345
  synchronize do |client|
1948
2346
  client.call([:hlen, key])
1949
2347
  end
1950
2348
  end
1951
2349
 
1952
- # Set the string value of a hash field.
2350
+ # Set one or more hash values.
2351
+ #
2352
+ # @example
2353
+ # redis.hset("hash", "f1", "v1", "f2", "v2") # => 2
2354
+ # redis.hset("hash", { "f1" => "v1", "f2" => "v2" }) # => 2
1953
2355
  #
1954
2356
  # @param [String] key
1955
- # @param [String] field
1956
- # @param [String] value
1957
- # @return [Boolean] whether or not the field was **added** to the hash
1958
- def hset(key, field, value)
2357
+ # @param [Array<String> | Hash<String, String>] attrs array or hash of fields and values
2358
+ # @return [Integer] The number of fields that were added to the hash
2359
+ def hset(key, *attrs)
2360
+ attrs = attrs.first.flatten if attrs.size == 1 && attrs.first.is_a?(Hash)
2361
+
1959
2362
  synchronize do |client|
1960
- client.call([:hset, key, field, value], &Boolify)
2363
+ client.call([:hset, key, *attrs])
1961
2364
  end
1962
2365
  end
1963
2366
 
@@ -2046,7 +2449,7 @@ class Redis
2046
2449
  # @see #hmget
2047
2450
  def mapped_hmget(key, *fields)
2048
2451
  hmget(key, *fields) do |reply|
2049
- if reply.kind_of?(Array)
2452
+ if reply.is_a?(Array)
2050
2453
  Hash[fields.zip(reply)]
2051
2454
  else
2052
2455
  reply
@@ -2058,10 +2461,10 @@ class Redis
2058
2461
  #
2059
2462
  # @param [String] key
2060
2463
  # @param [String, Array<String>] field
2061
- # @return [Fixnum] the number of fields that were removed from the hash
2062
- def hdel(key, field)
2464
+ # @return [Integer] the number of fields that were removed from the hash
2465
+ def hdel(key, *fields)
2063
2466
  synchronize do |client|
2064
- client.call([:hdel, key, field])
2467
+ client.call([:hdel, key, *fields])
2065
2468
  end
2066
2469
  end
2067
2470
 
@@ -2080,8 +2483,8 @@ class Redis
2080
2483
  #
2081
2484
  # @param [String] key
2082
2485
  # @param [String] field
2083
- # @param [Fixnum] increment
2084
- # @return [Fixnum] value of the field after incrementing it
2486
+ # @param [Integer] increment
2487
+ # @return [Integer] value of the field after incrementing it
2085
2488
  def hincrby(key, field, increment)
2086
2489
  synchronize do |client|
2087
2490
  client.call([:hincrby, key, field, increment])
@@ -2139,20 +2542,21 @@ class Redis
2139
2542
 
2140
2543
  def subscribed?
2141
2544
  synchronize do |client|
2142
- client.kind_of? SubscribedClient
2545
+ client.is_a? SubscribedClient
2143
2546
  end
2144
2547
  end
2145
2548
 
2146
2549
  # Listen for messages published to the given channels.
2147
2550
  def subscribe(*channels, &block)
2148
- synchronize do |client|
2551
+ synchronize do |_client|
2149
2552
  _subscription(:subscribe, 0, channels, block)
2150
2553
  end
2151
2554
  end
2152
2555
 
2153
- # Listen for messages published to the given channels. Throw a timeout error if there is no messages for a timeout period.
2556
+ # Listen for messages published to the given channels. Throw a timeout error
2557
+ # if there is no messages for a timeout period.
2154
2558
  def subscribe_with_timeout(timeout, *channels, &block)
2155
- synchronize do |client|
2559
+ synchronize do |_client|
2156
2560
  _subscription(:subscribe_with_timeout, timeout, channels, block)
2157
2561
  end
2158
2562
  end
@@ -2160,21 +2564,23 @@ class Redis
2160
2564
  # Stop listening for messages posted to the given channels.
2161
2565
  def unsubscribe(*channels)
2162
2566
  synchronize do |client|
2163
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2567
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
2568
+
2164
2569
  client.unsubscribe(*channels)
2165
2570
  end
2166
2571
  end
2167
2572
 
2168
2573
  # Listen for messages published to channels matching the given patterns.
2169
2574
  def psubscribe(*channels, &block)
2170
- synchronize do |client|
2575
+ synchronize do |_client|
2171
2576
  _subscription(:psubscribe, 0, channels, block)
2172
2577
  end
2173
2578
  end
2174
2579
 
2175
- # Listen for messages published to channels matching the given patterns. Throw a timeout error if there is no messages for a timeout period.
2580
+ # Listen for messages published to channels matching the given patterns.
2581
+ # Throw a timeout error if there is no messages for a timeout period.
2176
2582
  def psubscribe_with_timeout(timeout, *channels, &block)
2177
- synchronize do |client|
2583
+ synchronize do |_client|
2178
2584
  _subscription(:psubscribe_with_timeout, timeout, channels, block)
2179
2585
  end
2180
2586
  end
@@ -2182,7 +2588,8 @@ class Redis
2182
2588
  # Stop listening for messages posted to channels matching the given patterns.
2183
2589
  def punsubscribe(*channels)
2184
2590
  synchronize do |client|
2185
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2591
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
2592
+
2186
2593
  client.punsubscribe(*channels)
2187
2594
  end
2188
2595
  end
@@ -2227,7 +2634,7 @@ class Redis
2227
2634
  # @see #multi
2228
2635
  def watch(*keys)
2229
2636
  synchronize do |client|
2230
- res = client.call([:watch] + keys)
2637
+ res = client.call([:watch, *keys])
2231
2638
 
2232
2639
  if block_given?
2233
2640
  begin
@@ -2257,13 +2664,13 @@ class Redis
2257
2664
  end
2258
2665
 
2259
2666
  def pipelined
2260
- synchronize do |client|
2667
+ synchronize do |prior_client|
2261
2668
  begin
2262
- original, @client = @client, Pipeline.new
2669
+ @client = Pipeline.new(prior_client)
2263
2670
  yield(self)
2264
- original.call_pipeline(@client)
2671
+ prior_client.call_pipeline(@client)
2265
2672
  ensure
2266
- @client = original
2673
+ @client = prior_client
2267
2674
  end
2268
2675
  end
2269
2676
  end
@@ -2299,17 +2706,16 @@ class Redis
2299
2706
  # @see #watch
2300
2707
  # @see #unwatch
2301
2708
  def multi
2302
- synchronize do |client|
2709
+ synchronize do |prior_client|
2303
2710
  if !block_given?
2304
- client.call([:multi])
2711
+ prior_client.call([:multi])
2305
2712
  else
2306
2713
  begin
2307
- pipeline = Pipeline::Multi.new
2308
- original, @client = @client, pipeline
2714
+ @client = Pipeline::Multi.new(prior_client)
2309
2715
  yield(self)
2310
- original.call_pipeline(pipeline)
2716
+ prior_client.call_pipeline(@client)
2311
2717
  ensure
2312
- @client = original
2718
+ @client = prior_client
2313
2719
  end
2314
2720
  end
2315
2721
  end
@@ -2456,18 +2862,13 @@ class Redis
2456
2862
  _eval(:evalsha, args)
2457
2863
  end
2458
2864
 
2459
- def _scan(command, cursor, args, options = {}, &block)
2865
+ def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
2460
2866
  # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
2461
2867
 
2462
2868
  args << cursor
2463
-
2464
- if match = options[:match]
2465
- args.concat(["MATCH", match])
2466
- end
2467
-
2468
- if count = options[:count]
2469
- args.concat(["COUNT", count])
2470
- end
2869
+ args << "MATCH" << match if match
2870
+ args << "COUNT" << count if count
2871
+ args << "TYPE" << type if type
2471
2872
 
2472
2873
  synchronize do |client|
2473
2874
  client.call([command] + args, &block)
@@ -2482,15 +2883,19 @@ class Redis
2482
2883
  # @example Retrieve a batch of keys matching a pattern
2483
2884
  # redis.scan(4, :match => "key:1?")
2484
2885
  # # => ["92", ["key:13", "key:18"]]
2886
+ # @example Retrieve a batch of keys of a certain type
2887
+ # redis.scan(92, :type => "zset")
2888
+ # # => ["173", ["sortedset:14", "sortedset:78"]]
2485
2889
  #
2486
2890
  # @param [String, Integer] cursor the cursor of the iteration
2487
2891
  # @param [Hash] options
2488
2892
  # - `:match => String`: only return keys matching the pattern
2489
2893
  # - `:count => Integer`: return count keys at most per iteration
2894
+ # - `:type => String`: return keys only of the given type
2490
2895
  #
2491
2896
  # @return [String, Array<String>] the next cursor and all found keys
2492
- def scan(cursor, options={})
2493
- _scan(:scan, cursor, [], options)
2897
+ def scan(cursor, **options)
2898
+ _scan(:scan, cursor, [], **options)
2494
2899
  end
2495
2900
 
2496
2901
  # Scan the keyspace
@@ -2502,17 +2907,23 @@ class Redis
2502
2907
  # redis.scan_each(:match => "key:1?") {|key| puts key}
2503
2908
  # # => key:13
2504
2909
  # # => key:18
2910
+ # @example Execute block for each key of a type
2911
+ # redis.scan_each(:type => "hash") {|key| puts redis.type(key)}
2912
+ # # => "hash"
2913
+ # # => "hash"
2505
2914
  #
2506
2915
  # @param [Hash] options
2507
2916
  # - `:match => String`: only return keys matching the pattern
2508
2917
  # - `:count => Integer`: return count keys at most per iteration
2918
+ # - `:type => String`: return keys only of the given type
2509
2919
  #
2510
2920
  # @return [Enumerator] an enumerator for all found keys
2511
- def scan_each(options={}, &block)
2512
- return to_enum(:scan_each, options) unless block_given?
2921
+ def scan_each(**options, &block)
2922
+ return to_enum(:scan_each, **options) unless block_given?
2923
+
2513
2924
  cursor = 0
2514
2925
  loop do
2515
- cursor, keys = scan(cursor, options)
2926
+ cursor, keys = scan(cursor, **options)
2516
2927
  keys.each(&block)
2517
2928
  break if cursor == "0"
2518
2929
  end
@@ -2529,8 +2940,8 @@ class Redis
2529
2940
  # - `:count => Integer`: return count keys at most per iteration
2530
2941
  #
2531
2942
  # @return [String, Array<[String, String]>] the next cursor and all found keys
2532
- def hscan(key, cursor, options={})
2533
- _scan(:hscan, cursor, [key], options) do |reply|
2943
+ def hscan(key, cursor, **options)
2944
+ _scan(:hscan, cursor, [key], **options) do |reply|
2534
2945
  [reply[0], reply[1].each_slice(2).to_a]
2535
2946
  end
2536
2947
  end
@@ -2546,11 +2957,12 @@ class Redis
2546
2957
  # - `:count => Integer`: return count keys at most per iteration
2547
2958
  #
2548
2959
  # @return [Enumerator] an enumerator for all found keys
2549
- def hscan_each(key, options={}, &block)
2550
- return to_enum(:hscan_each, key, options) unless block_given?
2960
+ def hscan_each(key, **options, &block)
2961
+ return to_enum(:hscan_each, key, **options) unless block_given?
2962
+
2551
2963
  cursor = 0
2552
2964
  loop do
2553
- cursor, values = hscan(key, cursor, options)
2965
+ cursor, values = hscan(key, cursor, **options)
2554
2966
  values.each(&block)
2555
2967
  break if cursor == "0"
2556
2968
  end
@@ -2568,8 +2980,8 @@ class Redis
2568
2980
  #
2569
2981
  # @return [String, Array<[String, Float]>] the next cursor and all found
2570
2982
  # members and scores
2571
- def zscan(key, cursor, options={})
2572
- _scan(:zscan, cursor, [key], options) do |reply|
2983
+ def zscan(key, cursor, **options)
2984
+ _scan(:zscan, cursor, [key], **options) do |reply|
2573
2985
  [reply[0], FloatifyPairs.call(reply[1])]
2574
2986
  end
2575
2987
  end
@@ -2585,11 +2997,12 @@ class Redis
2585
2997
  # - `:count => Integer`: return count keys at most per iteration
2586
2998
  #
2587
2999
  # @return [Enumerator] an enumerator for all found scores and members
2588
- def zscan_each(key, options={}, &block)
2589
- return to_enum(:zscan_each, key, options) unless block_given?
3000
+ def zscan_each(key, **options, &block)
3001
+ return to_enum(:zscan_each, key, **options) unless block_given?
3002
+
2590
3003
  cursor = 0
2591
3004
  loop do
2592
- cursor, values = zscan(key, cursor, options)
3005
+ cursor, values = zscan(key, cursor, **options)
2593
3006
  values.each(&block)
2594
3007
  break if cursor == "0"
2595
3008
  end
@@ -2606,8 +3019,8 @@ class Redis
2606
3019
  # - `:count => Integer`: return count keys at most per iteration
2607
3020
  #
2608
3021
  # @return [String, Array<String>] the next cursor and all found members
2609
- def sscan(key, cursor, options={})
2610
- _scan(:sscan, cursor, [key], options)
3022
+ def sscan(key, cursor, **options)
3023
+ _scan(:sscan, cursor, [key], **options)
2611
3024
  end
2612
3025
 
2613
3026
  # Scan a set
@@ -2621,11 +3034,12 @@ class Redis
2621
3034
  # - `:count => Integer`: return count keys at most per iteration
2622
3035
  #
2623
3036
  # @return [Enumerator] an enumerator for all keys in the set
2624
- def sscan_each(key, options={}, &block)
2625
- return to_enum(:sscan_each, key, options) unless block_given?
3037
+ def sscan_each(key, **options, &block)
3038
+ return to_enum(:sscan_each, key, **options) unless block_given?
3039
+
2626
3040
  cursor = 0
2627
3041
  loop do
2628
- cursor, keys = sscan(key, cursor, options)
3042
+ cursor, keys = sscan(key, cursor, **options)
2629
3043
  keys.each(&block)
2630
3044
  break if cursor == "0"
2631
3045
  end
@@ -2648,7 +3062,7 @@ class Redis
2648
3062
  # union of the HyperLogLogs contained in the keys.
2649
3063
  #
2650
3064
  # @param [String, Array<String>] keys
2651
- # @return [Fixnum]
3065
+ # @return [Integer]
2652
3066
  def pfcount(*keys)
2653
3067
  synchronize do |client|
2654
3068
  client.call([:pfcount] + keys)
@@ -2667,6 +3081,445 @@ class Redis
2667
3081
  end
2668
3082
  end
2669
3083
 
3084
+ # Adds the specified geospatial items (latitude, longitude, name) to the specified key
3085
+ #
3086
+ # @param [String] key
3087
+ # @param [Array] member arguemnts for member or members: longitude, latitude, name
3088
+ # @return [Integer] number of elements added to the sorted set
3089
+ def geoadd(key, *member)
3090
+ synchronize do |client|
3091
+ client.call([:geoadd, key, *member])
3092
+ end
3093
+ end
3094
+
3095
+ # Returns geohash string representing position for specified members of the specified key.
3096
+ #
3097
+ # @param [String] key
3098
+ # @param [String, Array<String>] member one member or array of members
3099
+ # @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
3100
+ def geohash(key, member)
3101
+ synchronize do |client|
3102
+ client.call([:geohash, key, member])
3103
+ end
3104
+ end
3105
+
3106
+ # Query a sorted set representing a geospatial index to fetch members matching a
3107
+ # given maximum distance from a point
3108
+ #
3109
+ # @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
3110
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest
3111
+ # or the farthest to the nearest relative to the center
3112
+ # @param [Integer] count limit the results to the first N matching items
3113
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
3114
+ # @return [Array<String>] may be changed with `options`
3115
+
3116
+ def georadius(*args, **geoptions)
3117
+ geoarguments = _geoarguments(*args, **geoptions)
3118
+
3119
+ synchronize do |client|
3120
+ client.call([:georadius, *geoarguments])
3121
+ end
3122
+ end
3123
+
3124
+ # Query a sorted set representing a geospatial index to fetch members matching a
3125
+ # given maximum distance from an already existing member
3126
+ #
3127
+ # @param [Array] args key, member, radius, unit(m|km|ft|mi)
3128
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest
3129
+ # to the nearest relative to the center
3130
+ # @param [Integer] count limit the results to the first N matching items
3131
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
3132
+ # @return [Array<String>] may be changed with `options`
3133
+
3134
+ def georadiusbymember(*args, **geoptions)
3135
+ geoarguments = _geoarguments(*args, **geoptions)
3136
+
3137
+ synchronize do |client|
3138
+ client.call([:georadiusbymember, *geoarguments])
3139
+ end
3140
+ end
3141
+
3142
+ # Returns longitude and latitude of members of a geospatial index
3143
+ #
3144
+ # @param [String] key
3145
+ # @param [String, Array<String>] member one member or array of members
3146
+ # @return [Array<Array<String>, nil>] returns array of elements, where each
3147
+ # element is either array of longitude and latitude or nil
3148
+ def geopos(key, member)
3149
+ synchronize do |client|
3150
+ client.call([:geopos, key, member])
3151
+ end
3152
+ end
3153
+
3154
+ # Returns the distance between two members of a geospatial index
3155
+ #
3156
+ # @param [String ]key
3157
+ # @param [Array<String>] members
3158
+ # @param ['m', 'km', 'mi', 'ft'] unit
3159
+ # @return [String, nil] returns distance in spefied unit if both members present, nil otherwise.
3160
+ def geodist(key, member1, member2, unit = 'm')
3161
+ synchronize do |client|
3162
+ client.call([:geodist, key, member1, member2, unit])
3163
+ end
3164
+ end
3165
+
3166
+ # Returns the stream information each subcommand.
3167
+ #
3168
+ # @example stream
3169
+ # redis.xinfo(:stream, 'mystream')
3170
+ # @example groups
3171
+ # redis.xinfo(:groups, 'mystream')
3172
+ # @example consumers
3173
+ # redis.xinfo(:consumers, 'mystream', 'mygroup')
3174
+ #
3175
+ # @param subcommand [String] e.g. `stream` `groups` `consumers`
3176
+ # @param key [String] the stream key
3177
+ # @param group [String] the consumer group name, required if subcommand is `consumers`
3178
+ #
3179
+ # @return [Hash] information of the stream if subcommand is `stream`
3180
+ # @return [Array<Hash>] information of the consumer groups if subcommand is `groups`
3181
+ # @return [Array<Hash>] information of the consumers if subcommand is `consumers`
3182
+ def xinfo(subcommand, key, group = nil)
3183
+ args = [:xinfo, subcommand, key, group].compact
3184
+ synchronize do |client|
3185
+ client.call(args) do |reply|
3186
+ case subcommand.to_s.downcase
3187
+ when 'stream' then Hashify.call(reply)
3188
+ when 'groups', 'consumers' then reply.map { |arr| Hashify.call(arr) }
3189
+ else reply
3190
+ end
3191
+ end
3192
+ end
3193
+ end
3194
+
3195
+ # Add new entry to the stream.
3196
+ #
3197
+ # @example Without options
3198
+ # redis.xadd('mystream', f1: 'v1', f2: 'v2')
3199
+ # @example With options
3200
+ # redis.xadd('mystream', { f1: 'v1', f2: 'v2' }, id: '0-0', maxlen: 1000, approximate: true)
3201
+ #
3202
+ # @param key [String] the stream key
3203
+ # @param entry [Hash] one or multiple field-value pairs
3204
+ # @param opts [Hash] several options for `XADD` command
3205
+ #
3206
+ # @option opts [String] :id the entry id, default value is `*`, it means auto generation
3207
+ # @option opts [Integer] :maxlen max length of entries
3208
+ # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
3209
+ #
3210
+ # @return [String] the entry id
3211
+ def xadd(key, entry, approximate: nil, maxlen: nil, id: '*')
3212
+ args = [:xadd, key]
3213
+ if maxlen
3214
+ args << "MAXLEN"
3215
+ args << "~" if approximate
3216
+ args << maxlen
3217
+ end
3218
+ args << id
3219
+ args.concat(entry.to_a.flatten)
3220
+ synchronize { |client| client.call(args) }
3221
+ end
3222
+
3223
+ # Trims older entries of the stream if needed.
3224
+ #
3225
+ # @example Without options
3226
+ # redis.xtrim('mystream', 1000)
3227
+ # @example With options
3228
+ # redis.xtrim('mystream', 1000, approximate: true)
3229
+ #
3230
+ # @param key [String] the stream key
3231
+ # @param mexlen [Integer] max length of entries
3232
+ # @param approximate [Boolean] whether to add `~` modifier of maxlen or not
3233
+ #
3234
+ # @return [Integer] the number of entries actually deleted
3235
+ def xtrim(key, maxlen, approximate: false)
3236
+ args = [:xtrim, key, 'MAXLEN', (approximate ? '~' : nil), maxlen].compact
3237
+ synchronize { |client| client.call(args) }
3238
+ end
3239
+
3240
+ # Delete entries by entry ids.
3241
+ #
3242
+ # @example With splatted entry ids
3243
+ # redis.xdel('mystream', '0-1', '0-2')
3244
+ # @example With arrayed entry ids
3245
+ # redis.xdel('mystream', ['0-1', '0-2'])
3246
+ #
3247
+ # @param key [String] the stream key
3248
+ # @param ids [Array<String>] one or multiple entry ids
3249
+ #
3250
+ # @return [Integer] the number of entries actually deleted
3251
+ def xdel(key, *ids)
3252
+ args = [:xdel, key].concat(ids.flatten)
3253
+ synchronize { |client| client.call(args) }
3254
+ end
3255
+
3256
+ # Fetches entries of the stream in ascending order.
3257
+ #
3258
+ # @example Without options
3259
+ # redis.xrange('mystream')
3260
+ # @example With a specific start
3261
+ # redis.xrange('mystream', '0-1')
3262
+ # @example With a specific start and end
3263
+ # redis.xrange('mystream', '0-1', '0-3')
3264
+ # @example With count options
3265
+ # redis.xrange('mystream', count: 10)
3266
+ #
3267
+ # @param key [String] the stream key
3268
+ # @param start [String] first entry id of range, default value is `-`
3269
+ # @param end [String] last entry id of range, default value is `+`
3270
+ # @param count [Integer] the number of entries as limit
3271
+ #
3272
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
3273
+ def xrange(key, start = '-', range_end = '+', count: nil)
3274
+ args = [:xrange, key, start, range_end]
3275
+ args.concat(['COUNT', count]) if count
3276
+ synchronize { |client| client.call(args, &HashifyStreamEntries) }
3277
+ end
3278
+
3279
+ # Fetches entries of the stream in descending order.
3280
+ #
3281
+ # @example Without options
3282
+ # redis.xrevrange('mystream')
3283
+ # @example With a specific end
3284
+ # redis.xrevrange('mystream', '0-3')
3285
+ # @example With a specific end and start
3286
+ # redis.xrevrange('mystream', '0-3', '0-1')
3287
+ # @example With count options
3288
+ # redis.xrevrange('mystream', count: 10)
3289
+ #
3290
+ # @param key [String] the stream key
3291
+ # @param end [String] first entry id of range, default value is `+`
3292
+ # @param start [String] last entry id of range, default value is `-`
3293
+ # @params count [Integer] the number of entries as limit
3294
+ #
3295
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
3296
+ def xrevrange(key, range_end = '+', start = '-', count: nil)
3297
+ args = [:xrevrange, key, range_end, start]
3298
+ args.concat(['COUNT', count]) if count
3299
+ synchronize { |client| client.call(args, &HashifyStreamEntries) }
3300
+ end
3301
+
3302
+ # Returns the number of entries inside a stream.
3303
+ #
3304
+ # @example With key
3305
+ # redis.xlen('mystream')
3306
+ #
3307
+ # @param key [String] the stream key
3308
+ #
3309
+ # @return [Integer] the number of entries
3310
+ def xlen(key)
3311
+ synchronize { |client| client.call([:xlen, key]) }
3312
+ end
3313
+
3314
+ # Fetches entries from one or multiple streams. Optionally blocking.
3315
+ #
3316
+ # @example With a key
3317
+ # redis.xread('mystream', '0-0')
3318
+ # @example With multiple keys
3319
+ # redis.xread(%w[mystream1 mystream2], %w[0-0 0-0])
3320
+ # @example With count option
3321
+ # redis.xread('mystream', '0-0', count: 2)
3322
+ # @example With block option
3323
+ # redis.xread('mystream', '$', block: 1000)
3324
+ #
3325
+ # @param keys [Array<String>] one or multiple stream keys
3326
+ # @param ids [Array<String>] one or multiple entry ids
3327
+ # @param count [Integer] the number of entries as limit per stream
3328
+ # @param block [Integer] the number of milliseconds as blocking timeout
3329
+ #
3330
+ # @return [Hash{String => Hash{String => Hash}}] the entries
3331
+ def xread(keys, ids, count: nil, block: nil)
3332
+ args = [:xread]
3333
+ args << 'COUNT' << count if count
3334
+ args << 'BLOCK' << block.to_i if block
3335
+ _xread(args, keys, ids, block)
3336
+ end
3337
+
3338
+ # Manages the consumer group of the stream.
3339
+ #
3340
+ # @example With `create` subcommand
3341
+ # redis.xgroup(:create, 'mystream', 'mygroup', '$')
3342
+ # @example With `setid` subcommand
3343
+ # redis.xgroup(:setid, 'mystream', 'mygroup', '$')
3344
+ # @example With `destroy` subcommand
3345
+ # redis.xgroup(:destroy, 'mystream', 'mygroup')
3346
+ # @example With `delconsumer` subcommand
3347
+ # redis.xgroup(:delconsumer, 'mystream', 'mygroup', 'consumer1')
3348
+ #
3349
+ # @param subcommand [String] `create` `setid` `destroy` `delconsumer`
3350
+ # @param key [String] the stream key
3351
+ # @param group [String] the consumer group name
3352
+ # @param id_or_consumer [String]
3353
+ # * the entry id or `$`, required if subcommand is `create` or `setid`
3354
+ # * the consumer name, required if subcommand is `delconsumer`
3355
+ # @param mkstream [Boolean] whether to create an empty stream automatically or not
3356
+ #
3357
+ # @return [String] `OK` if subcommand is `create` or `setid`
3358
+ # @return [Integer] effected count if subcommand is `destroy` or `delconsumer`
3359
+ def xgroup(subcommand, key, group, id_or_consumer = nil, mkstream: false)
3360
+ args = [:xgroup, subcommand, key, group, id_or_consumer, (mkstream ? 'MKSTREAM' : nil)].compact
3361
+ synchronize { |client| client.call(args) }
3362
+ end
3363
+
3364
+ # Fetches a subset of the entries from one or multiple streams related with the consumer group.
3365
+ # Optionally blocking.
3366
+ #
3367
+ # @example With a key
3368
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>')
3369
+ # @example With multiple keys
3370
+ # redis.xreadgroup('mygroup', 'consumer1', %w[mystream1 mystream2], %w[> >])
3371
+ # @example With count option
3372
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', count: 2)
3373
+ # @example With block option
3374
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', block: 1000)
3375
+ # @example With noack option
3376
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', noack: true)
3377
+ #
3378
+ # @param group [String] the consumer group name
3379
+ # @param consumer [String] the consumer name
3380
+ # @param keys [Array<String>] one or multiple stream keys
3381
+ # @param ids [Array<String>] one or multiple entry ids
3382
+ # @param opts [Hash] several options for `XREADGROUP` command
3383
+ #
3384
+ # @option opts [Integer] :count the number of entries as limit
3385
+ # @option opts [Integer] :block the number of milliseconds as blocking timeout
3386
+ # @option opts [Boolean] :noack whether message loss is acceptable or not
3387
+ #
3388
+ # @return [Hash{String => Hash{String => Hash}}] the entries
3389
+ def xreadgroup(group, consumer, keys, ids, count: nil, block: nil, noack: nil)
3390
+ args = [:xreadgroup, 'GROUP', group, consumer]
3391
+ args << 'COUNT' << count if count
3392
+ args << 'BLOCK' << block.to_i if block
3393
+ args << 'NOACK' if noack
3394
+ _xread(args, keys, ids, block)
3395
+ end
3396
+
3397
+ # Removes one or multiple entries from the pending entries list of a stream consumer group.
3398
+ #
3399
+ # @example With a entry id
3400
+ # redis.xack('mystream', 'mygroup', '1526569495631-0')
3401
+ # @example With splatted entry ids
3402
+ # redis.xack('mystream', 'mygroup', '0-1', '0-2')
3403
+ # @example With arrayed entry ids
3404
+ # redis.xack('mystream', 'mygroup', %w[0-1 0-2])
3405
+ #
3406
+ # @param key [String] the stream key
3407
+ # @param group [String] the consumer group name
3408
+ # @param ids [Array<String>] one or multiple entry ids
3409
+ #
3410
+ # @return [Integer] the number of entries successfully acknowledged
3411
+ def xack(key, group, *ids)
3412
+ args = [:xack, key, group].concat(ids.flatten)
3413
+ synchronize { |client| client.call(args) }
3414
+ end
3415
+
3416
+ # Changes the ownership of a pending entry
3417
+ #
3418
+ # @example With splatted entry ids
3419
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-1', '0-2')
3420
+ # @example With arrayed entry ids
3421
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2])
3422
+ # @example With idle option
3423
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], idle: 1000)
3424
+ # @example With time option
3425
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], time: 1542866959000)
3426
+ # @example With retrycount option
3427
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], retrycount: 10)
3428
+ # @example With force option
3429
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], force: true)
3430
+ # @example With justid option
3431
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], justid: true)
3432
+ #
3433
+ # @param key [String] the stream key
3434
+ # @param group [String] the consumer group name
3435
+ # @param consumer [String] the consumer name
3436
+ # @param min_idle_time [Integer] the number of milliseconds
3437
+ # @param ids [Array<String>] one or multiple entry ids
3438
+ # @param opts [Hash] several options for `XCLAIM` command
3439
+ #
3440
+ # @option opts [Integer] :idle the number of milliseconds as last time it was delivered of the entry
3441
+ # @option opts [Integer] :time the number of milliseconds as a specific Unix Epoch time
3442
+ # @option opts [Integer] :retrycount the number of retry counter
3443
+ # @option opts [Boolean] :force whether to create the pending entry to the pending entries list or not
3444
+ # @option opts [Boolean] :justid whether to fetch just an array of entry ids or not
3445
+ #
3446
+ # @return [Hash{String => Hash}] the entries successfully claimed
3447
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
3448
+ def xclaim(key, group, consumer, min_idle_time, *ids, **opts)
3449
+ args = [:xclaim, key, group, consumer, min_idle_time].concat(ids.flatten)
3450
+ args.concat(['IDLE', opts[:idle].to_i]) if opts[:idle]
3451
+ args.concat(['TIME', opts[:time].to_i]) if opts[:time]
3452
+ args.concat(['RETRYCOUNT', opts[:retrycount]]) if opts[:retrycount]
3453
+ args << 'FORCE' if opts[:force]
3454
+ args << 'JUSTID' if opts[:justid]
3455
+ blk = opts[:justid] ? Noop : HashifyStreamEntries
3456
+ synchronize { |client| client.call(args, &blk) }
3457
+ end
3458
+
3459
+ # Transfers ownership of pending stream entries that match the specified criteria.
3460
+ #
3461
+ # @example Claim next pending message stuck > 5 minutes and mark as retry
3462
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0')
3463
+ # @example Claim 50 next pending messages stuck > 5 minutes and mark as retry
3464
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', count: 50)
3465
+ # @example Claim next pending message stuck > 5 minutes and don't mark as retry
3466
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-0', justid: true)
3467
+ # @example Claim next pending message after this id stuck > 5 minutes and mark as retry
3468
+ # redis.xautoclaim('mystream', 'mygroup', 'consumer1', 3600000, '1641321233-0')
3469
+ #
3470
+ # @param key [String] the stream key
3471
+ # @param group [String] the consumer group name
3472
+ # @param consumer [String] the consumer name
3473
+ # @param min_idle_time [Integer] the number of milliseconds
3474
+ # @param start [String] entry id to start scanning from or 0-0 for everything
3475
+ # @param count [Integer] number of messages to claim (default 1)
3476
+ # @param justid [Boolean] whether to fetch just an array of entry ids or not.
3477
+ # Does not increment retry count when true
3478
+ #
3479
+ # @return [Hash{String => Hash}] the entries successfully claimed
3480
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
3481
+ def xautoclaim(key, group, consumer, min_idle_time, start, count: nil, justid: false)
3482
+ args = [:xautoclaim, key, group, consumer, min_idle_time, start]
3483
+ if count
3484
+ args << 'COUNT' << count.to_s
3485
+ end
3486
+ args << 'JUSTID' if justid
3487
+ blk = justid ? HashifyStreamAutoclaimJustId : HashifyStreamAutoclaim
3488
+ synchronize { |client| client.call(args, &blk) }
3489
+ end
3490
+
3491
+ # Fetches not acknowledging pending entries
3492
+ #
3493
+ # @example With key and group
3494
+ # redis.xpending('mystream', 'mygroup')
3495
+ # @example With range options
3496
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10)
3497
+ # @example With range and consumer options
3498
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, 'consumer1')
3499
+ #
3500
+ # @param key [String] the stream key
3501
+ # @param group [String] the consumer group name
3502
+ # @param start [String] start first entry id of range
3503
+ # @param end [String] end last entry id of range
3504
+ # @param count [Integer] count the number of entries as limit
3505
+ # @param consumer [String] the consumer name
3506
+ #
3507
+ # @return [Hash] the summary of pending entries
3508
+ # @return [Array<Hash>] the pending entries details if options were specified
3509
+ def xpending(key, group, *args)
3510
+ command_args = [:xpending, key, group]
3511
+ case args.size
3512
+ when 0, 3, 4
3513
+ command_args.concat(args)
3514
+ else
3515
+ raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 2, 5 or 6)"
3516
+ end
3517
+
3518
+ summary_needed = args.empty?
3519
+ blk = summary_needed ? HashifyStreamPendings : HashifyStreamPendingDetails
3520
+ synchronize { |client| client.call(command_args, &blk) }
3521
+ end
3522
+
2670
3523
  # Interact with the sentinel command (masters, master, slaves, failover)
2671
3524
  #
2672
3525
  # @param [String] subcommand e.g. `masters`, `master`, `slaves`
@@ -2680,8 +3533,8 @@ class Redis
2680
3533
  when "get-master-addr-by-name"
2681
3534
  reply
2682
3535
  else
2683
- if reply.kind_of?(Array)
2684
- if reply[0].kind_of?(Array)
3536
+ if reply.is_a?(Array)
3537
+ if reply[0].is_a?(Array)
2685
3538
  reply.map(&Hashify)
2686
3539
  else
2687
3540
  Hashify.call(reply)
@@ -2694,6 +3547,46 @@ class Redis
2694
3547
  end
2695
3548
  end
2696
3549
 
3550
+ # Sends `CLUSTER *` command to random node and returns its reply.
3551
+ #
3552
+ # @see https://redis.io/commands#cluster Reference of cluster command
3553
+ #
3554
+ # @param subcommand [String, Symbol] the subcommand of cluster command
3555
+ # e.g. `:slots`, `:nodes`, `:slaves`, `:info`
3556
+ #
3557
+ # @return [Object] depends on the subcommand
3558
+ def cluster(subcommand, *args)
3559
+ subcommand = subcommand.to_s.downcase
3560
+ block = case subcommand
3561
+ when 'slots'
3562
+ HashifyClusterSlots
3563
+ when 'nodes'
3564
+ HashifyClusterNodes
3565
+ when 'slaves'
3566
+ HashifyClusterSlaves
3567
+ when 'info'
3568
+ HashifyInfo
3569
+ else
3570
+ Noop
3571
+ end
3572
+
3573
+ # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
3574
+ block = Noop unless @cluster_mode
3575
+
3576
+ synchronize do |client|
3577
+ client.call([:cluster, subcommand] + args, &block)
3578
+ end
3579
+ end
3580
+
3581
+ # Sends `ASKING` command to random node and returns its reply.
3582
+ #
3583
+ # @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
3584
+ #
3585
+ # @return [String] `'OK'`
3586
+ def asking
3587
+ synchronize { |client| client.call(%i[asking]) }
3588
+ end
3589
+
2697
3590
  def id
2698
3591
  @original_client.id
2699
3592
  end
@@ -2706,59 +3599,184 @@ class Redis
2706
3599
  self.class.new(@options)
2707
3600
  end
2708
3601
 
2709
- def method_missing(command, *args)
3602
+ def connection
3603
+ return @original_client.connection_info if @cluster_mode
3604
+
3605
+ {
3606
+ host: @original_client.host,
3607
+ port: @original_client.port,
3608
+ db: @original_client.db,
3609
+ id: @original_client.id,
3610
+ location: @original_client.location
3611
+ }
3612
+ end
3613
+
3614
+ def method_missing(command, *args) # rubocop:disable Style/MissingRespondToMissing
2710
3615
  synchronize do |client|
2711
3616
  client.call([command] + args)
2712
3617
  end
2713
3618
  end
2714
3619
 
2715
- private
3620
+ private
2716
3621
 
2717
3622
  # Commands returning 1 for true and 0 for false may be executed in a pipeline
2718
3623
  # where the method call will return nil. Propagate the nil instead of falsely
2719
3624
  # returning false.
2720
- Boolify =
2721
- lambda { |value|
2722
- value == 1 if value
2723
- }
3625
+ Boolify = lambda { |value|
3626
+ case value
3627
+ when 1
3628
+ true
3629
+ when 0
3630
+ false
3631
+ else
3632
+ value
3633
+ end
3634
+ }
2724
3635
 
2725
- BoolifySet =
2726
- lambda { |value|
2727
- if value && "OK" == value
2728
- true
2729
- else
2730
- false
2731
- end
2732
- }
3636
+ BoolifySet = lambda { |value|
3637
+ case value
3638
+ when "OK"
3639
+ true
3640
+ when nil
3641
+ false
3642
+ else
3643
+ value
3644
+ end
3645
+ }
2733
3646
 
2734
- Hashify =
2735
- lambda { |array|
2736
- hash = Hash.new
2737
- array.each_slice(2) do |field, value|
2738
- hash[field] = value
2739
- end
2740
- hash
2741
- }
3647
+ Hashify = lambda { |value|
3648
+ if value.respond_to?(:each_slice)
3649
+ value.each_slice(2).to_h
3650
+ else
3651
+ value
3652
+ end
3653
+ }
3654
+
3655
+ Floatify = lambda { |value|
3656
+ case value
3657
+ when "inf"
3658
+ Float::INFINITY
3659
+ when "-inf"
3660
+ -Float::INFINITY
3661
+ when String
3662
+ Float(value)
3663
+ else
3664
+ value
3665
+ end
3666
+ }
2742
3667
 
2743
- Floatify =
2744
- lambda { |str|
2745
- if str
2746
- if (inf = str.match(/^(-)?inf/i))
2747
- (inf[1] ? -1.0 : 1.0) / 0.0
2748
- else
2749
- Float(str)
2750
- end
2751
- end
3668
+ FloatifyPairs = lambda { |value|
3669
+ return value unless value.respond_to?(:each_slice)
3670
+
3671
+ value.each_slice(2).map do |member, score|
3672
+ [member, Floatify.call(score)]
3673
+ end
3674
+ }
3675
+
3676
+ HashifyInfo = lambda { |reply|
3677
+ lines = reply.split("\r\n").grep_v(/^(#|$)/)
3678
+ lines.map! { |line| line.split(':', 2) }
3679
+ lines.compact!
3680
+ lines.to_h
3681
+ }
3682
+
3683
+ HashifyStreams = lambda { |reply|
3684
+ case reply
3685
+ when nil
3686
+ {}
3687
+ else
3688
+ reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h
3689
+ end
3690
+ }
3691
+
3692
+ EMPTY_STREAM_RESPONSE = [nil].freeze
3693
+ private_constant :EMPTY_STREAM_RESPONSE
3694
+
3695
+ HashifyStreamEntries = lambda { |reply|
3696
+ reply.compact.map do |entry_id, values|
3697
+ [entry_id, values&.each_slice(2)&.to_h]
3698
+ end
3699
+ }
3700
+
3701
+ HashifyStreamAutoclaim = lambda { |reply|
3702
+ {
3703
+ 'next' => reply[0],
3704
+ 'entries' => reply[1].map { |entry| [entry[0], entry[1].each_slice(2).to_h] }
2752
3705
  }
3706
+ }
2753
3707
 
2754
- FloatifyPairs =
2755
- lambda { |array|
2756
- if array
2757
- array.each_slice(2).map do |member, score|
2758
- [member, Floatify.call(score)]
2759
- end
2760
- end
3708
+ HashifyStreamAutoclaimJustId = lambda { |reply|
3709
+ {
3710
+ 'next' => reply[0],
3711
+ 'entries' => reply[1]
3712
+ }
3713
+ }
3714
+
3715
+ HashifyStreamPendings = lambda { |reply|
3716
+ {
3717
+ 'size' => reply[0],
3718
+ 'min_entry_id' => reply[1],
3719
+ 'max_entry_id' => reply[2],
3720
+ 'consumers' => reply[3].nil? ? {} : reply[3].to_h
2761
3721
  }
3722
+ }
3723
+
3724
+ HashifyStreamPendingDetails = lambda { |reply|
3725
+ reply.map do |arr|
3726
+ {
3727
+ 'entry_id' => arr[0],
3728
+ 'consumer' => arr[1],
3729
+ 'elapsed' => arr[2],
3730
+ 'count' => arr[3]
3731
+ }
3732
+ end
3733
+ }
3734
+
3735
+ HashifyClusterNodeInfo = lambda { |str|
3736
+ arr = str.split(' ')
3737
+ {
3738
+ 'node_id' => arr[0],
3739
+ 'ip_port' => arr[1],
3740
+ 'flags' => arr[2].split(','),
3741
+ 'master_node_id' => arr[3],
3742
+ 'ping_sent' => arr[4],
3743
+ 'pong_recv' => arr[5],
3744
+ 'config_epoch' => arr[6],
3745
+ 'link_state' => arr[7],
3746
+ 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
3747
+ }
3748
+ }
3749
+
3750
+ HashifyClusterSlots = lambda { |reply|
3751
+ reply.map do |arr|
3752
+ first_slot, last_slot = arr[0..1]
3753
+ master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
3754
+ replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
3755
+ {
3756
+ 'start_slot' => first_slot,
3757
+ 'end_slot' => last_slot,
3758
+ 'master' => master,
3759
+ 'replicas' => replicas
3760
+ }
3761
+ end
3762
+ }
3763
+
3764
+ HashifyClusterNodes = lambda { |reply|
3765
+ reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
3766
+ }
3767
+
3768
+ HashifyClusterSlaves = lambda { |reply|
3769
+ reply.map { |str| HashifyClusterNodeInfo.call(str) }
3770
+ }
3771
+
3772
+ Noop = ->(reply) { reply }
3773
+
3774
+ def _geoarguments(*args, options: nil, sort: nil, count: nil)
3775
+ args.push sort if sort
3776
+ args.push 'count', count if count
3777
+ args.push options if options
3778
+ args
3779
+ end
2762
3780
 
2763
3781
  def _subscription(method, timeout, channels, block)
2764
3782
  return @client.call([method] + channels) if subscribed?
@@ -2775,10 +3793,44 @@ private
2775
3793
  end
2776
3794
  end
2777
3795
 
3796
+ def _xread(args, keys, ids, blocking_timeout_msec)
3797
+ keys = keys.is_a?(Array) ? keys : [keys]
3798
+ ids = ids.is_a?(Array) ? ids : [ids]
3799
+ args << 'STREAMS'
3800
+ args.concat(keys)
3801
+ args.concat(ids)
3802
+
3803
+ synchronize do |client|
3804
+ if blocking_timeout_msec.nil?
3805
+ client.call(args, &HashifyStreams)
3806
+ elsif blocking_timeout_msec.to_f.zero?
3807
+ client.call_without_timeout(args, &HashifyStreams)
3808
+ else
3809
+ timeout = client.timeout.to_f + blocking_timeout_msec.to_f / 1000.0
3810
+ client.call_with_timeout(args, timeout, &HashifyStreams)
3811
+ end
3812
+ end
3813
+ end
3814
+
3815
+ def _normalize_move_wheres(where_source, where_destination)
3816
+ where_source = where_source.to_s.upcase
3817
+ where_destination = where_destination.to_s.upcase
3818
+
3819
+ if where_source != "LEFT" && where_source != "RIGHT"
3820
+ raise ArgumentError, "where_source must be 'LEFT' or 'RIGHT'"
3821
+ end
3822
+
3823
+ if where_destination != "LEFT" && where_destination != "RIGHT"
3824
+ raise ArgumentError, "where_destination must be 'LEFT' or 'RIGHT'"
3825
+ end
3826
+
3827
+ [where_source, where_destination]
3828
+ end
2778
3829
  end
2779
3830
 
2780
3831
  require_relative "redis/version"
2781
3832
  require_relative "redis/connection"
2782
3833
  require_relative "redis/client"
3834
+ require_relative "redis/cluster"
2783
3835
  require_relative "redis/pipeline"
2784
3836
  require_relative "redis/subscribe"