redis 4.0.3 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +110 -0
  3. data/README.md +126 -17
  4. data/lib/redis/client.rb +130 -82
  5. data/lib/redis/cluster/command_loader.rb +8 -7
  6. data/lib/redis/cluster/node.rb +5 -1
  7. data/lib/redis/cluster/node_key.rb +3 -7
  8. data/lib/redis/cluster/node_loader.rb +2 -0
  9. data/lib/redis/cluster/option.rb +31 -14
  10. data/lib/redis/cluster/slot.rb +30 -13
  11. data/lib/redis/cluster/slot_loader.rb +6 -4
  12. data/lib/redis/cluster.rb +23 -17
  13. data/lib/redis/connection/command_helper.rb +5 -2
  14. data/lib/redis/connection/hiredis.rb +4 -3
  15. data/lib/redis/connection/registry.rb +2 -1
  16. data/lib/redis/connection/ruby.rb +139 -106
  17. data/lib/redis/connection/synchrony.rb +9 -4
  18. data/lib/redis/connection.rb +2 -0
  19. data/lib/redis/distributed.rb +171 -70
  20. data/lib/redis/errors.rb +2 -0
  21. data/lib/redis/hash_ring.rb +15 -14
  22. data/lib/redis/pipeline.rb +46 -8
  23. data/lib/redis/subscribe.rb +11 -12
  24. data/lib/redis/version.rb +3 -1
  25. data/lib/redis.rb +1239 -426
  26. metadata +16 -262
  27. data/.gitignore +0 -19
  28. data/.travis/Gemfile +0 -18
  29. data/.travis.yml +0 -61
  30. data/.yardopts +0 -3
  31. data/Gemfile +0 -8
  32. data/benchmarking/logging.rb +0 -71
  33. data/benchmarking/pipeline.rb +0 -51
  34. data/benchmarking/speed.rb +0 -21
  35. data/benchmarking/suite.rb +0 -24
  36. data/benchmarking/worker.rb +0 -71
  37. data/bin/build +0 -71
  38. data/bors.toml +0 -14
  39. data/examples/basic.rb +0 -15
  40. data/examples/consistency.rb +0 -114
  41. data/examples/dist_redis.rb +0 -43
  42. data/examples/incr-decr.rb +0 -17
  43. data/examples/list.rb +0 -26
  44. data/examples/pubsub.rb +0 -37
  45. data/examples/sentinel/sentinel.conf +0 -9
  46. data/examples/sentinel/start +0 -49
  47. data/examples/sentinel.rb +0 -41
  48. data/examples/sets.rb +0 -36
  49. data/examples/unicorn/config.ru +0 -3
  50. data/examples/unicorn/unicorn.rb +0 -20
  51. data/makefile +0 -74
  52. data/redis.gemspec +0 -43
  53. data/test/bitpos_test.rb +0 -63
  54. data/test/blocking_commands_test.rb +0 -40
  55. data/test/client_test.rb +0 -76
  56. data/test/cluster_abnormal_state_test.rb +0 -38
  57. data/test/cluster_blocking_commands_test.rb +0 -15
  58. data/test/cluster_client_internals_test.rb +0 -77
  59. data/test/cluster_client_key_hash_tags_test.rb +0 -88
  60. data/test/cluster_client_options_test.rb +0 -147
  61. data/test/cluster_client_pipelining_test.rb +0 -59
  62. data/test/cluster_client_replicas_test.rb +0 -36
  63. data/test/cluster_client_slots_test.rb +0 -94
  64. data/test/cluster_client_transactions_test.rb +0 -71
  65. data/test/cluster_commands_on_cluster_test.rb +0 -165
  66. data/test/cluster_commands_on_connection_test.rb +0 -40
  67. data/test/cluster_commands_on_geo_test.rb +0 -74
  68. data/test/cluster_commands_on_hashes_test.rb +0 -11
  69. data/test/cluster_commands_on_hyper_log_log_test.rb +0 -17
  70. data/test/cluster_commands_on_keys_test.rb +0 -134
  71. data/test/cluster_commands_on_lists_test.rb +0 -15
  72. data/test/cluster_commands_on_pub_sub_test.rb +0 -101
  73. data/test/cluster_commands_on_scripting_test.rb +0 -56
  74. data/test/cluster_commands_on_server_test.rb +0 -221
  75. data/test/cluster_commands_on_sets_test.rb +0 -39
  76. data/test/cluster_commands_on_sorted_sets_test.rb +0 -35
  77. data/test/cluster_commands_on_streams_test.rb +0 -196
  78. data/test/cluster_commands_on_strings_test.rb +0 -15
  79. data/test/cluster_commands_on_transactions_test.rb +0 -41
  80. data/test/cluster_commands_on_value_types_test.rb +0 -14
  81. data/test/command_map_test.rb +0 -28
  82. data/test/commands_on_geo_test.rb +0 -116
  83. data/test/commands_on_hashes_test.rb +0 -7
  84. data/test/commands_on_hyper_log_log_test.rb +0 -7
  85. data/test/commands_on_lists_test.rb +0 -7
  86. data/test/commands_on_sets_test.rb +0 -7
  87. data/test/commands_on_sorted_sets_test.rb +0 -7
  88. data/test/commands_on_strings_test.rb +0 -7
  89. data/test/commands_on_value_types_test.rb +0 -207
  90. data/test/connection_handling_test.rb +0 -275
  91. data/test/connection_test.rb +0 -57
  92. data/test/db/.gitkeep +0 -0
  93. data/test/distributed_blocking_commands_test.rb +0 -52
  94. data/test/distributed_commands_on_hashes_test.rb +0 -21
  95. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -26
  96. data/test/distributed_commands_on_lists_test.rb +0 -19
  97. data/test/distributed_commands_on_sets_test.rb +0 -105
  98. data/test/distributed_commands_on_sorted_sets_test.rb +0 -59
  99. data/test/distributed_commands_on_strings_test.rb +0 -79
  100. data/test/distributed_commands_on_value_types_test.rb +0 -129
  101. data/test/distributed_commands_requiring_clustering_test.rb +0 -162
  102. data/test/distributed_connection_handling_test.rb +0 -21
  103. data/test/distributed_internals_test.rb +0 -68
  104. data/test/distributed_key_tags_test.rb +0 -50
  105. data/test/distributed_persistence_control_commands_test.rb +0 -24
  106. data/test/distributed_publish_subscribe_test.rb +0 -90
  107. data/test/distributed_remote_server_control_commands_test.rb +0 -64
  108. data/test/distributed_scripting_test.rb +0 -100
  109. data/test/distributed_sorting_test.rb +0 -18
  110. data/test/distributed_test.rb +0 -56
  111. data/test/distributed_transactions_test.rb +0 -30
  112. data/test/encoding_test.rb +0 -14
  113. data/test/error_replies_test.rb +0 -57
  114. data/test/fork_safety_test.rb +0 -60
  115. data/test/helper.rb +0 -345
  116. data/test/helper_test.rb +0 -22
  117. data/test/internals_test.rb +0 -408
  118. data/test/lint/blocking_commands.rb +0 -174
  119. data/test/lint/hashes.rb +0 -203
  120. data/test/lint/hyper_log_log.rb +0 -74
  121. data/test/lint/lists.rb +0 -159
  122. data/test/lint/sets.rb +0 -282
  123. data/test/lint/sorted_sets.rb +0 -497
  124. data/test/lint/strings.rb +0 -348
  125. data/test/lint/value_types.rb +0 -130
  126. data/test/persistence_control_commands_test.rb +0 -24
  127. data/test/pipelining_commands_test.rb +0 -246
  128. data/test/publish_subscribe_test.rb +0 -280
  129. data/test/remote_server_control_commands_test.rb +0 -175
  130. data/test/scanning_test.rb +0 -407
  131. data/test/scripting_test.rb +0 -76
  132. data/test/sentinel_command_test.rb +0 -78
  133. data/test/sentinel_test.rb +0 -253
  134. data/test/sorting_test.rb +0 -57
  135. data/test/ssl_test.rb +0 -69
  136. data/test/support/cluster/orchestrator.rb +0 -199
  137. data/test/support/connection/hiredis.rb +0 -1
  138. data/test/support/connection/ruby.rb +0 -1
  139. data/test/support/connection/synchrony.rb +0 -17
  140. data/test/support/redis_mock.rb +0 -130
  141. data/test/support/ssl/gen_certs.sh +0 -31
  142. data/test/support/ssl/trusted-ca.crt +0 -25
  143. data/test/support/ssl/trusted-ca.key +0 -27
  144. data/test/support/ssl/trusted-cert.crt +0 -81
  145. data/test/support/ssl/trusted-cert.key +0 -28
  146. data/test/support/ssl/untrusted-ca.crt +0 -26
  147. data/test/support/ssl/untrusted-ca.key +0 -27
  148. data/test/support/ssl/untrusted-cert.crt +0 -82
  149. data/test/support/ssl/untrusted-cert.key +0 -28
  150. data/test/support/wire/synchrony.rb +0 -24
  151. data/test/support/wire/thread.rb +0 -5
  152. data/test/synchrony_driver.rb +0 -85
  153. data/test/test.conf.erb +0 -9
  154. data/test/thread_safety_test.rb +0 -60
  155. data/test/transactions_test.rb +0 -272
  156. data/test/unknown_commands_test.rb +0 -12
  157. 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,18 +33,23 @@ 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`
@@ -51,7 +73,7 @@ class Redis
51
73
  end
52
74
 
53
75
  # Run code with the client reconnecting
54
- def with_reconnect(val=true, &blk)
76
+ def with_reconnect(val = true, &blk)
55
77
  synchronize do |client|
56
78
  client.with_reconnect(val, &blk)
57
79
  end
@@ -94,7 +116,9 @@ class Redis
94
116
  # See http://redis.io/topics/pipelining for more details.
95
117
  #
96
118
  def queue(*command)
97
- @queue[Thread.current.object_id] << command
119
+ synchronize do
120
+ @queue[Thread.current.object_id] << command
121
+ end
98
122
  end
99
123
 
100
124
  # Sends all commands in the queue.
@@ -104,7 +128,12 @@ class Redis
104
128
  def commit
105
129
  synchronize do |client|
106
130
  begin
107
- 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)
108
137
  ensure
109
138
  @queue.delete(Thread.current.object_id)
110
139
  end
@@ -117,18 +146,19 @@ class Redis
117
146
 
118
147
  # Authenticate to the server.
119
148
  #
120
- # @param [String] password must match the password specified in the
121
- # `requirepass` directive in the configuration file
149
+ # @param [Array<String>] args includes both username and password
150
+ # or only password
122
151
  # @return [String] `OK`
123
- def auth(password)
152
+ # @see https://redis.io/commands/auth AUTH command
153
+ def auth(*args)
124
154
  synchronize do |client|
125
- client.call([:auth, password])
155
+ client.call([:auth, *args])
126
156
  end
127
157
  end
128
158
 
129
159
  # Change the selected database for the current connection.
130
160
  #
131
- # @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)
132
162
  # @return [String] `OK`
133
163
  def select(db)
134
164
  synchronize do |client|
@@ -197,7 +227,7 @@ class Redis
197
227
  def config(action, *args)
198
228
  synchronize do |client|
199
229
  client.call([:config, action] + args) do |reply|
200
- if reply.kind_of?(Array) && action == :get
230
+ if reply.is_a?(Array) && action == :get
201
231
  Hashify.call(reply)
202
232
  else
203
233
  reply
@@ -227,7 +257,7 @@ class Redis
227
257
 
228
258
  # Return the number of keys in the selected database.
229
259
  #
230
- # @return [Fixnum]
260
+ # @return [Integer]
231
261
  def dbsize
232
262
  synchronize do |client|
233
263
  client.call([:dbsize])
@@ -248,7 +278,7 @@ class Redis
248
278
  def flushall(options = nil)
249
279
  synchronize do |client|
250
280
  if options && options[:async]
251
- client.call([:flushall, :async])
281
+ client.call(%i[flushall async])
252
282
  else
253
283
  client.call([:flushall])
254
284
  end
@@ -263,7 +293,7 @@ class Redis
263
293
  def flushdb(options = nil)
264
294
  synchronize do |client|
265
295
  if options && options[:async]
266
- client.call([:flushdb, :async])
296
+ client.call(%i[flushdb async])
267
297
  else
268
298
  client.call([:flushdb])
269
299
  end
@@ -277,7 +307,7 @@ class Redis
277
307
  def info(cmd = nil)
278
308
  synchronize do |client|
279
309
  client.call([:info, cmd].compact) do |reply|
280
- if reply.kind_of?(String)
310
+ if reply.is_a?(String)
281
311
  reply = HashifyInfo.call(reply)
282
312
 
283
313
  if cmd && cmd.to_s == "commandstats"
@@ -296,7 +326,7 @@ class Redis
296
326
 
297
327
  # Get the UNIX time stamp of the last successful save to disk.
298
328
  #
299
- # @return [Fixnum]
329
+ # @return [Integer]
300
330
  def lastsave
301
331
  synchronize do |client|
302
332
  client.call([:lastsave])
@@ -348,9 +378,9 @@ class Redis
348
378
  # Interact with the slowlog (get, len, reset)
349
379
  #
350
380
  # @param [String] subcommand e.g. `get`, `len`, `reset`
351
- # @param [Fixnum] length maximum number of entries to return
352
- # @return [Array<String>, Fixnum, String] depends on subcommand
353
- def slowlog(subcommand, length=nil)
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)
354
384
  synchronize do |client|
355
385
  args = [:slowlog, subcommand]
356
386
  args << length if length
@@ -370,12 +400,12 @@ class Redis
370
400
  # @example
371
401
  # r.time # => [ 1333093196, 606806 ]
372
402
  #
373
- # @return [Array<Fixnum>] tuple of seconds since UNIX epoch and
403
+ # @return [Array<Integer>] tuple of seconds since UNIX epoch and
374
404
  # microseconds in the current second
375
405
  def time
376
406
  synchronize do |client|
377
407
  client.call([:time]) do |reply|
378
- reply.map(&:to_i) if reply
408
+ reply&.map(&:to_i)
379
409
  end
380
410
  end
381
411
  end
@@ -393,7 +423,7 @@ class Redis
393
423
  # Set a key's time to live in seconds.
394
424
  #
395
425
  # @param [String] key
396
- # @param [Fixnum] seconds time to live
426
+ # @param [Integer] seconds time to live
397
427
  # @return [Boolean] whether the timeout was set or not
398
428
  def expire(key, seconds)
399
429
  synchronize do |client|
@@ -404,7 +434,7 @@ class Redis
404
434
  # Set the expiration for a key as a UNIX timestamp.
405
435
  #
406
436
  # @param [String] key
407
- # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp
437
+ # @param [Integer] unix_time expiry time specified as a UNIX timestamp
408
438
  # @return [Boolean] whether the timeout was set or not
409
439
  def expireat(key, unix_time)
410
440
  synchronize do |client|
@@ -415,7 +445,7 @@ class Redis
415
445
  # Get the time to live (in seconds) for a key.
416
446
  #
417
447
  # @param [String] key
418
- # @return [Fixnum] remaining time to live in seconds.
448
+ # @return [Integer] remaining time to live in seconds.
419
449
  #
420
450
  # In Redis 2.6 or older the command returns -1 if the key does not exist or if
421
451
  # the key exist but has no associated expire.
@@ -433,7 +463,7 @@ class Redis
433
463
  # Set a key's time to live in milliseconds.
434
464
  #
435
465
  # @param [String] key
436
- # @param [Fixnum] milliseconds time to live
466
+ # @param [Integer] milliseconds time to live
437
467
  # @return [Boolean] whether the timeout was set or not
438
468
  def pexpire(key, milliseconds)
439
469
  synchronize do |client|
@@ -444,7 +474,7 @@ class Redis
444
474
  # Set the expiration for a key as number of milliseconds from UNIX Epoch.
445
475
  #
446
476
  # @param [String] key
447
- # @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.
448
478
  # @return [Boolean] whether the timeout was set or not
449
479
  def pexpireat(key, ms_unix_time)
450
480
  synchronize do |client|
@@ -455,7 +485,7 @@ class Redis
455
485
  # Get the time to live (in milliseconds) for a key.
456
486
  #
457
487
  # @param [String] key
458
- # @return [Fixnum] remaining time to live in milliseconds
488
+ # @return [Integer] remaining time to live in milliseconds
459
489
  # In Redis 2.6 or older the command returns -1 if the key does not exist or if
460
490
  # the key exist but has no associated expire.
461
491
  #
@@ -488,9 +518,9 @@ class Redis
488
518
  # - `:replace => Boolean`: if false, raises an error if key already exists
489
519
  # @raise [Redis::CommandError]
490
520
  # @return [String] `"OK"`
491
- def restore(key, ttl, serialized_value, options = {})
521
+ def restore(key, ttl, serialized_value, replace: nil)
492
522
  args = [:restore, key, ttl, serialized_value]
493
- args << 'REPLACE' if options[:replace]
523
+ args << 'REPLACE' if replace
494
524
 
495
525
  synchronize do |client|
496
526
  client.call(args)
@@ -499,29 +529,37 @@ class Redis
499
529
 
500
530
  # Transfer a key from the connected instance to another instance.
501
531
  #
502
- # @param [String] key
532
+ # @param [String, Array<String>] key
503
533
  # @param [Hash] options
504
534
  # - `:host => String`: host of instance to migrate to
505
535
  # - `:port => Integer`: port of instance to migrate to
506
536
  # - `:db => Integer`: database to migrate to (default: same as source)
507
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.
508
540
  # @return [String] `"OK"`
509
541
  def migrate(key, options)
510
- host = options[:host] || raise(RuntimeError, ":host not specified")
511
- port = options[:port] || raise(RuntimeError, ":port not specified")
512
- db = (options[:db] || @client.db).to_i
513
- 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)
514
551
 
515
- synchronize do |client|
516
- client.call([:migrate, host, port, key, db, timeout])
517
- end
552
+ synchronize { |client| client.call(args) }
518
553
  end
519
554
 
520
555
  # Delete one or more keys.
521
556
  #
522
557
  # @param [String, Array<String>] keys
523
- # @return [Fixnum] number of keys that were deleted
558
+ # @return [Integer] number of keys that were deleted
524
559
  def del(*keys)
560
+ keys.flatten!(1)
561
+ return 0 if keys.empty?
562
+
525
563
  synchronize do |client|
526
564
  client.call([:del] + keys)
527
565
  end
@@ -530,20 +568,50 @@ class Redis
530
568
  # Unlink one or more keys.
531
569
  #
532
570
  # @param [String, Array<String>] keys
533
- # @return [Fixnum] number of keys that were unlinked
571
+ # @return [Integer] number of keys that were unlinked
534
572
  def unlink(*keys)
535
573
  synchronize do |client|
536
574
  client.call([:unlink] + keys)
537
575
  end
538
576
  end
539
577
 
540
- # Determine if a key exists.
578
+ # Determine how many of the keys exists.
541
579
  #
542
- # @param [String] key
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
543
609
  # @return [Boolean]
544
- def exists(key)
610
+ def exists?(*keys)
545
611
  synchronize do |client|
546
- client.call([:exists, key], &Boolify)
612
+ client.call([:exists, *keys]) do |value|
613
+ value > 0
614
+ end
547
615
  end
548
616
  end
549
617
 
@@ -554,7 +622,7 @@ class Redis
554
622
  def keys(pattern = "*")
555
623
  synchronize do |client|
556
624
  client.call([:keys, pattern]) do |reply|
557
- if reply.kind_of?(String)
625
+ if reply.is_a?(String)
558
626
  reply.split(" ")
559
627
  else
560
628
  reply
@@ -580,7 +648,7 @@ class Redis
580
648
  # # => "bar"
581
649
  #
582
650
  # @param [String] key
583
- # @param [Fixnum] db
651
+ # @param [Integer] db
584
652
  # @return [Boolean] whether the key was moved or not
585
653
  def move(key, db)
586
654
  synchronize do |client|
@@ -644,36 +712,33 @@ class Redis
644
712
  # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
645
713
  # - `:store => String`: key to store the result at
646
714
  #
647
- # @return [Array<String>, Array<Array<String>>, Fixnum]
715
+ # @return [Array<String>, Array<Array<String>>, Integer]
648
716
  # - when `:get` is not specified, or holds a single element, an array of elements
649
717
  # - when `:get` is specified, and holds more than one element, an array of
650
718
  # elements where every element is an array with the result for every
651
719
  # element specified in `:get`
652
720
  # - when `:store` is specified, the number of elements in the stored result
653
- def sort(key, options = {})
654
- args = []
721
+ def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
722
+ args = [:sort, key]
723
+ args << "BY" << by if by
655
724
 
656
- by = options[:by]
657
- args.concat(["BY", by]) if by
658
-
659
- limit = options[:limit]
660
- args.concat(["LIMIT"] + limit) if limit
725
+ if limit
726
+ args << "LIMIT"
727
+ args.concat(limit)
728
+ end
661
729
 
662
- get = Array(options[:get])
663
- args.concat(["GET"].product(get).flatten) unless get.empty?
730
+ get = Array(get)
731
+ get.each do |item|
732
+ args << "GET" << item
733
+ end
664
734
 
665
- order = options[:order]
666
735
  args.concat(order.split(" ")) if order
667
-
668
- store = options[:store]
669
- args.concat(["STORE", store]) if store
736
+ args << "STORE" << store if store
670
737
 
671
738
  synchronize do |client|
672
- client.call([:sort, key] + args) do |reply|
739
+ client.call(args) do |reply|
673
740
  if get.size > 1 && !store
674
- if reply
675
- reply.each_slice(get.size).to_a
676
- end
741
+ reply.each_slice(get.size).to_a if reply
677
742
  else
678
743
  reply
679
744
  end
@@ -698,7 +763,7 @@ class Redis
698
763
  # # => 4
699
764
  #
700
765
  # @param [String] key
701
- # @return [Fixnum] value after decrementing it
766
+ # @return [Integer] value after decrementing it
702
767
  def decr(key)
703
768
  synchronize do |client|
704
769
  client.call([:decr, key])
@@ -712,8 +777,8 @@ class Redis
712
777
  # # => 0
713
778
  #
714
779
  # @param [String] key
715
- # @param [Fixnum] decrement
716
- # @return [Fixnum] value after decrementing it
780
+ # @param [Integer] decrement
781
+ # @return [Integer] value after decrementing it
717
782
  def decrby(key, decrement)
718
783
  synchronize do |client|
719
784
  client.call([:decrby, key, decrement])
@@ -727,7 +792,7 @@ class Redis
727
792
  # # => 6
728
793
  #
729
794
  # @param [String] key
730
- # @return [Fixnum] value after incrementing it
795
+ # @return [Integer] value after incrementing it
731
796
  def incr(key)
732
797
  synchronize do |client|
733
798
  client.call([:incr, key])
@@ -741,8 +806,8 @@ class Redis
741
806
  # # => 10
742
807
  #
743
808
  # @param [String] key
744
- # @param [Fixnum] increment
745
- # @return [Fixnum] value after incrementing it
809
+ # @param [Integer] increment
810
+ # @return [Integer] value after incrementing it
746
811
  def incrby(key, increment)
747
812
  synchronize do |client|
748
813
  client.call([:incrby, key, increment])
@@ -769,31 +834,31 @@ class Redis
769
834
  # @param [String] key
770
835
  # @param [String] value
771
836
  # @param [Hash] options
772
- # - `:ex => Fixnum`: Set the specified expire time, in seconds.
773
- # - `: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.
774
841
  # - `:nx => true`: Only set the key if it does not already exist.
775
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.
776
845
  # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
777
- def set(key, value, options = {})
778
- args = []
779
-
780
- ex = options[:ex]
781
- args.concat(["EX", ex]) if ex
782
-
783
- px = options[:px]
784
- args.concat(["PX", px]) if px
785
-
786
- nx = options[:nx]
787
- args.concat(["NX"]) if nx
788
-
789
- xx = options[:xx]
790
- 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
791
856
 
792
857
  synchronize do |client|
793
858
  if nx || xx
794
- client.call([:set, key, value.to_s] + args, &BoolifySet)
859
+ client.call(args, &BoolifySet)
795
860
  else
796
- client.call([:set, key, value.to_s] + args)
861
+ client.call(args)
797
862
  end
798
863
  end
799
864
  end
@@ -801,7 +866,7 @@ class Redis
801
866
  # Set the time to live in seconds of a key.
802
867
  #
803
868
  # @param [String] key
804
- # @param [Fixnum] ttl
869
+ # @param [Integer] ttl
805
870
  # @param [String] value
806
871
  # @return [String] `"OK"`
807
872
  def setex(key, ttl, value)
@@ -813,7 +878,7 @@ class Redis
813
878
  # Set the time to live in milliseconds of a key.
814
879
  #
815
880
  # @param [String] key
816
- # @param [Fixnum] ttl
881
+ # @param [Integer] ttl
817
882
  # @param [String] value
818
883
  # @return [String] `"OK"`
819
884
  def psetex(key, ttl, value)
@@ -875,7 +940,7 @@ class Redis
875
940
  # @see #mapped_msetnx
876
941
  def msetnx(*args)
877
942
  synchronize do |client|
878
- client.call([:msetnx] + args, &Boolify)
943
+ client.call([:msetnx, *args], &Boolify)
879
944
  end
880
945
  end
881
946
 
@@ -906,7 +971,7 @@ class Redis
906
971
  # Get the values of all the given keys.
907
972
  #
908
973
  # @example
909
- # redis.mget("key1", "key1")
974
+ # redis.mget("key1", "key2")
910
975
  # # => ["v1", "v2"]
911
976
  #
912
977
  # @param [Array<String>] keys
@@ -915,7 +980,7 @@ class Redis
915
980
  # @see #mapped_mget
916
981
  def mget(*keys, &blk)
917
982
  synchronize do |client|
918
- client.call([:mget] + keys, &blk)
983
+ client.call([:mget, *keys], &blk)
919
984
  end
920
985
  end
921
986
 
@@ -931,7 +996,7 @@ class Redis
931
996
  # @see #mget
932
997
  def mapped_mget(*keys)
933
998
  mget(*keys) do |reply|
934
- if reply.kind_of?(Array)
999
+ if reply.is_a?(Array)
935
1000
  Hash[keys.zip(reply)]
936
1001
  else
937
1002
  reply
@@ -942,9 +1007,9 @@ class Redis
942
1007
  # Overwrite part of a string at key starting at the specified offset.
943
1008
  #
944
1009
  # @param [String] key
945
- # @param [Fixnum] offset byte offset
1010
+ # @param [Integer] offset byte offset
946
1011
  # @param [String] value
947
- # @return [Fixnum] length of the string after it was modified
1012
+ # @return [Integer] length of the string after it was modified
948
1013
  def setrange(key, offset, value)
949
1014
  synchronize do |client|
950
1015
  client.call([:setrange, key, offset, value.to_s])
@@ -954,10 +1019,10 @@ class Redis
954
1019
  # Get a substring of the string stored at a key.
955
1020
  #
956
1021
  # @param [String] key
957
- # @param [Fixnum] start zero-based start offset
958
- # @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
959
1024
  # the end of the string
960
- # @return [Fixnum] `0` or `1`
1025
+ # @return [Integer] `0` or `1`
961
1026
  def getrange(key, start, stop)
962
1027
  synchronize do |client|
963
1028
  client.call([:getrange, key, start, stop])
@@ -967,9 +1032,9 @@ class Redis
967
1032
  # Sets or clears the bit at offset in the string value stored at key.
968
1033
  #
969
1034
  # @param [String] key
970
- # @param [Fixnum] offset bit offset
971
- # @param [Fixnum] value bit value `0` or `1`
972
- # @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`
973
1038
  def setbit(key, offset, value)
974
1039
  synchronize do |client|
975
1040
  client.call([:setbit, key, offset, value])
@@ -979,8 +1044,8 @@ class Redis
979
1044
  # Returns the bit value at offset in the string value stored at key.
980
1045
  #
981
1046
  # @param [String] key
982
- # @param [Fixnum] offset bit offset
983
- # @return [Fixnum] `0` or `1`
1047
+ # @param [Integer] offset bit offset
1048
+ # @return [Integer] `0` or `1`
984
1049
  def getbit(key, offset)
985
1050
  synchronize do |client|
986
1051
  client.call([:getbit, key, offset])
@@ -991,7 +1056,7 @@ class Redis
991
1056
  #
992
1057
  # @param [String] key
993
1058
  # @param [String] value value to append
994
- # @return [Fixnum] length of the string after appending
1059
+ # @return [Integer] length of the string after appending
995
1060
  def append(key, value)
996
1061
  synchronize do |client|
997
1062
  client.call([:append, key, value])
@@ -1001,9 +1066,9 @@ class Redis
1001
1066
  # Count the number of set bits in a range of the string value stored at key.
1002
1067
  #
1003
1068
  # @param [String] key
1004
- # @param [Fixnum] start start index
1005
- # @param [Fixnum] stop stop index
1006
- # @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
1007
1072
  def bitcount(key, start = 0, stop = -1)
1008
1073
  synchronize do |client|
1009
1074
  client.call([:bitcount, key, start, stop])
@@ -1015,25 +1080,23 @@ class Redis
1015
1080
  # @param [String] operation e.g. `and`, `or`, `xor`, `not`
1016
1081
  # @param [String] destkey destination key
1017
1082
  # @param [String, Array<String>] keys one or more source keys to perform `operation`
1018
- # @return [Fixnum] the length of the string stored in `destkey`
1083
+ # @return [Integer] the length of the string stored in `destkey`
1019
1084
  def bitop(operation, destkey, *keys)
1020
1085
  synchronize do |client|
1021
- client.call([:bitop, operation, destkey] + keys)
1086
+ client.call([:bitop, operation, destkey, *keys])
1022
1087
  end
1023
1088
  end
1024
1089
 
1025
1090
  # Return the position of the first bit set to 1 or 0 in a string.
1026
1091
  #
1027
1092
  # @param [String] key
1028
- # @param [Fixnum] bit whether to look for the first 1 or 0 bit
1029
- # @param [Fixnum] start start index
1030
- # @param [Fixnum] stop stop index
1031
- # @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.
1032
1097
  # -1 if looking for 1 and it is not found or start and stop are given.
1033
- def bitpos(key, bit, start=nil, stop=nil)
1034
- if stop and not start
1035
- raise(ArgumentError, 'stop parameter specified without start parameter')
1036
- end
1098
+ def bitpos(key, bit, start = nil, stop = nil)
1099
+ raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
1037
1100
 
1038
1101
  synchronize do |client|
1039
1102
  command = [:bitpos, key, bit]
@@ -1055,10 +1118,49 @@ class Redis
1055
1118
  end
1056
1119
  end
1057
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
+
1058
1160
  # Get the length of the value stored in a key.
1059
1161
  #
1060
1162
  # @param [String] key
1061
- # @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
1062
1164
  # if the key does not exist
1063
1165
  def strlen(key)
1064
1166
  synchronize do |client|
@@ -1069,18 +1171,71 @@ class Redis
1069
1171
  # Get the length of a list.
1070
1172
  #
1071
1173
  # @param [String] key
1072
- # @return [Fixnum]
1174
+ # @return [Integer]
1073
1175
  def llen(key)
1074
1176
  synchronize do |client|
1075
1177
  client.call([:llen, key])
1076
1178
  end
1077
1179
  end
1078
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
+
1079
1234
  # Prepend one or more values to a list, creating the list if it doesn't exist
1080
1235
  #
1081
1236
  # @param [String] key
1082
1237
  # @param [String, Array<String>] value string value, or array of string values to push
1083
- # @return [Fixnum] the length of the list after the push operation
1238
+ # @return [Integer] the length of the list after the push operation
1084
1239
  def lpush(key, value)
1085
1240
  synchronize do |client|
1086
1241
  client.call([:lpush, key, value])
@@ -1091,7 +1246,7 @@ class Redis
1091
1246
  #
1092
1247
  # @param [String] key
1093
1248
  # @param [String] value
1094
- # @return [Fixnum] the length of the list after the push operation
1249
+ # @return [Integer] the length of the list after the push operation
1095
1250
  def lpushx(key, value)
1096
1251
  synchronize do |client|
1097
1252
  client.call([:lpushx, key, value])
@@ -1102,7 +1257,7 @@ class Redis
1102
1257
  #
1103
1258
  # @param [String] key
1104
1259
  # @param [String, Array<String>] value string value, or array of string values to push
1105
- # @return [Fixnum] the length of the list after the push operation
1260
+ # @return [Integer] the length of the list after the push operation
1106
1261
  def rpush(key, value)
1107
1262
  synchronize do |client|
1108
1263
  client.call([:rpush, key, value])
@@ -1113,30 +1268,36 @@ class Redis
1113
1268
  #
1114
1269
  # @param [String] key
1115
1270
  # @param [String] value
1116
- # @return [Fixnum] the length of the list after the push operation
1271
+ # @return [Integer] the length of the list after the push operation
1117
1272
  def rpushx(key, value)
1118
1273
  synchronize do |client|
1119
1274
  client.call([:rpushx, key, value])
1120
1275
  end
1121
1276
  end
1122
1277
 
1123
- # Remove and get the first element in a list.
1278
+ # Remove and get the first elements in a list.
1124
1279
  #
1125
1280
  # @param [String] key
1126
- # @return [String]
1127
- 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)
1128
1284
  synchronize do |client|
1129
- client.call([:lpop, key])
1285
+ command = [:lpop, key]
1286
+ command << count if count
1287
+ client.call(command)
1130
1288
  end
1131
1289
  end
1132
1290
 
1133
- # Remove and get the last element in a list.
1291
+ # Remove and get the last elements in a list.
1134
1292
  #
1135
1293
  # @param [String] key
1136
- # @return [String]
1137
- 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)
1138
1297
  synchronize do |client|
1139
- client.call([:rpop, key])
1298
+ command = [:rpop, key]
1299
+ command << count if count
1300
+ client.call(command)
1140
1301
  end
1141
1302
  end
1142
1303
 
@@ -1151,28 +1312,27 @@ class Redis
1151
1312
  end
1152
1313
  end
1153
1314
 
1154
- def _bpop(cmd, args)
1155
- options = {}
1156
-
1157
- case args.last
1158
- when Hash
1315
+ def _bpop(cmd, args, &blk)
1316
+ timeout = if args.last.is_a?(Hash)
1159
1317
  options = args.pop
1160
- when Integer
1318
+ options[:timeout]
1319
+ elsif args.last.respond_to?(:to_int)
1161
1320
  # Issue deprecation notice in obnoxious mode...
1162
- options[:timeout] = args.pop
1321
+ args.pop.to_int
1163
1322
  end
1164
1323
 
1324
+ timeout ||= 0
1325
+
1165
1326
  if args.size > 1
1166
1327
  # Issue deprecation notice in obnoxious mode...
1167
1328
  end
1168
1329
 
1169
1330
  keys = args.flatten
1170
- timeout = options[:timeout] || 0
1171
1331
 
1172
1332
  synchronize do |client|
1173
1333
  command = [cmd, keys, timeout]
1174
1334
  timeout += client.timeout if timeout > 0
1175
- client.call_with_timeout(command, timeout)
1335
+ client.call_with_timeout(command, timeout, &blk)
1176
1336
  end
1177
1337
  end
1178
1338
 
@@ -1192,7 +1352,7 @@ class Redis
1192
1352
  # @param [String, Array<String>] keys one or more keys to perform the
1193
1353
  # blocking pop on
1194
1354
  # @param [Hash] options
1195
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1355
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1196
1356
  #
1197
1357
  # @return [nil, [String, String]]
1198
1358
  # - `nil` when the operation timed out
@@ -1206,7 +1366,7 @@ class Redis
1206
1366
  # @param [String, Array<String>] keys one or more keys to perform the
1207
1367
  # blocking pop on
1208
1368
  # @param [Hash] options
1209
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1369
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1210
1370
  #
1211
1371
  # @return [nil, [String, String]]
1212
1372
  # - `nil` when the operation timed out
@@ -1223,20 +1383,12 @@ class Redis
1223
1383
  # @param [String] source source key
1224
1384
  # @param [String] destination destination key
1225
1385
  # @param [Hash] options
1226
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1386
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1227
1387
  #
1228
1388
  # @return [nil, String]
1229
1389
  # - `nil` when the operation timed out
1230
1390
  # - the element was popped and pushed otherwise
1231
- def brpoplpush(source, destination, options = {})
1232
- case options
1233
- when Integer
1234
- # Issue deprecation notice in obnoxious mode...
1235
- options = { :timeout => options }
1236
- end
1237
-
1238
- timeout = options[:timeout] || 0
1239
-
1391
+ def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
1240
1392
  synchronize do |client|
1241
1393
  command = [:brpoplpush, source, destination, timeout]
1242
1394
  timeout += client.timeout if timeout > 0
@@ -1247,7 +1399,7 @@ class Redis
1247
1399
  # Get an element from a list by its index.
1248
1400
  #
1249
1401
  # @param [String] key
1250
- # @param [Fixnum] index
1402
+ # @param [Integer] index
1251
1403
  # @return [String]
1252
1404
  def lindex(key, index)
1253
1405
  synchronize do |client|
@@ -1261,7 +1413,7 @@ class Redis
1261
1413
  # @param [String, Symbol] where `BEFORE` or `AFTER`
1262
1414
  # @param [String] pivot reference element
1263
1415
  # @param [String] value
1264
- # @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`
1265
1417
  # when the element `pivot` was not found
1266
1418
  def linsert(key, where, pivot, value)
1267
1419
  synchronize do |client|
@@ -1272,8 +1424,8 @@ class Redis
1272
1424
  # Get a range of elements from a list.
1273
1425
  #
1274
1426
  # @param [String] key
1275
- # @param [Fixnum] start start index
1276
- # @param [Fixnum] stop stop index
1427
+ # @param [Integer] start start index
1428
+ # @param [Integer] stop stop index
1277
1429
  # @return [Array<String>]
1278
1430
  def lrange(key, start, stop)
1279
1431
  synchronize do |client|
@@ -1284,12 +1436,12 @@ class Redis
1284
1436
  # Remove elements from a list.
1285
1437
  #
1286
1438
  # @param [String] key
1287
- # @param [Fixnum] count number of elements to remove. Use a positive
1439
+ # @param [Integer] count number of elements to remove. Use a positive
1288
1440
  # value to remove the first `count` occurrences of `value`. A negative
1289
1441
  # value to remove the last `count` occurrences of `value`. Or zero, to
1290
1442
  # remove all occurrences of `value` from the list.
1291
1443
  # @param [String] value
1292
- # @return [Fixnum] the number of removed elements
1444
+ # @return [Integer] the number of removed elements
1293
1445
  def lrem(key, count, value)
1294
1446
  synchronize do |client|
1295
1447
  client.call([:lrem, key, count, value])
@@ -1299,7 +1451,7 @@ class Redis
1299
1451
  # Set the value of an element in a list by its index.
1300
1452
  #
1301
1453
  # @param [String] key
1302
- # @param [Fixnum] index
1454
+ # @param [Integer] index
1303
1455
  # @param [String] value
1304
1456
  # @return [String] `OK`
1305
1457
  def lset(key, index, value)
@@ -1311,8 +1463,8 @@ class Redis
1311
1463
  # Trim a list to the specified range.
1312
1464
  #
1313
1465
  # @param [String] key
1314
- # @param [Fixnum] start start index
1315
- # @param [Fixnum] stop stop index
1466
+ # @param [Integer] start start index
1467
+ # @param [Integer] stop stop index
1316
1468
  # @return [String] `OK`
1317
1469
  def ltrim(key, start, stop)
1318
1470
  synchronize do |client|
@@ -1323,7 +1475,7 @@ class Redis
1323
1475
  # Get the number of members in a set.
1324
1476
  #
1325
1477
  # @param [String] key
1326
- # @return [Fixnum]
1478
+ # @return [Integer]
1327
1479
  def scard(key)
1328
1480
  synchronize do |client|
1329
1481
  client.call([:scard, key])
@@ -1334,8 +1486,8 @@ class Redis
1334
1486
  #
1335
1487
  # @param [String] key
1336
1488
  # @param [String, Array<String>] member one member, or array of members
1337
- # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1338
- # 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
1339
1491
  # array of members is specified, holding the number of members that were
1340
1492
  # successfully added
1341
1493
  def sadd(key, member)
@@ -1356,8 +1508,8 @@ class Redis
1356
1508
  #
1357
1509
  # @param [String] key
1358
1510
  # @param [String, Array<String>] member one member, or array of members
1359
- # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1360
- # 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
1361
1513
  # array of members is specified, holding the number of members that were
1362
1514
  # successfully removed
1363
1515
  def srem(key, member)
@@ -1378,7 +1530,7 @@ class Redis
1378
1530
  #
1379
1531
  # @param [String] key
1380
1532
  # @return [String]
1381
- # @param [Fixnum] count
1533
+ # @param [Integer] count
1382
1534
  def spop(key, count = nil)
1383
1535
  synchronize do |client|
1384
1536
  if count.nil?
@@ -1392,7 +1544,7 @@ class Redis
1392
1544
  # Get one or more random members from a set.
1393
1545
  #
1394
1546
  # @param [String] key
1395
- # @param [Fixnum] count
1547
+ # @param [Integer] count
1396
1548
  # @return [String]
1397
1549
  def srandmember(key, count = nil)
1398
1550
  synchronize do |client|
@@ -1427,6 +1579,19 @@ class Redis
1427
1579
  end
1428
1580
  end
1429
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
+
1430
1595
  # Get all the members in a set.
1431
1596
  #
1432
1597
  # @param [String] key
@@ -1443,7 +1608,7 @@ class Redis
1443
1608
  # @return [Array<String>] members in the difference
1444
1609
  def sdiff(*keys)
1445
1610
  synchronize do |client|
1446
- client.call([:sdiff] + keys)
1611
+ client.call([:sdiff, *keys])
1447
1612
  end
1448
1613
  end
1449
1614
 
@@ -1451,10 +1616,10 @@ class Redis
1451
1616
  #
1452
1617
  # @param [String] destination destination key
1453
1618
  # @param [String, Array<String>] keys keys pointing to sets to subtract
1454
- # @return [Fixnum] number of elements in the resulting set
1619
+ # @return [Integer] number of elements in the resulting set
1455
1620
  def sdiffstore(destination, *keys)
1456
1621
  synchronize do |client|
1457
- client.call([:sdiffstore, destination] + keys)
1622
+ client.call([:sdiffstore, destination, *keys])
1458
1623
  end
1459
1624
  end
1460
1625
 
@@ -1464,7 +1629,7 @@ class Redis
1464
1629
  # @return [Array<String>] members in the intersection
1465
1630
  def sinter(*keys)
1466
1631
  synchronize do |client|
1467
- client.call([:sinter] + keys)
1632
+ client.call([:sinter, *keys])
1468
1633
  end
1469
1634
  end
1470
1635
 
@@ -1472,10 +1637,10 @@ class Redis
1472
1637
  #
1473
1638
  # @param [String] destination destination key
1474
1639
  # @param [String, Array<String>] keys keys pointing to sets to intersect
1475
- # @return [Fixnum] number of elements in the resulting set
1640
+ # @return [Integer] number of elements in the resulting set
1476
1641
  def sinterstore(destination, *keys)
1477
1642
  synchronize do |client|
1478
- client.call([:sinterstore, destination] + keys)
1643
+ client.call([:sinterstore, destination, *keys])
1479
1644
  end
1480
1645
  end
1481
1646
 
@@ -1485,7 +1650,7 @@ class Redis
1485
1650
  # @return [Array<String>] members in the union
1486
1651
  def sunion(*keys)
1487
1652
  synchronize do |client|
1488
- client.call([:sunion] + keys)
1653
+ client.call([:sunion, *keys])
1489
1654
  end
1490
1655
  end
1491
1656
 
@@ -1493,10 +1658,10 @@ class Redis
1493
1658
  #
1494
1659
  # @param [String] destination destination key
1495
1660
  # @param [String, Array<String>] keys keys pointing to sets to unify
1496
- # @return [Fixnum] number of elements in the resulting set
1661
+ # @return [Integer] number of elements in the resulting set
1497
1662
  def sunionstore(destination, *keys)
1498
1663
  synchronize do |client|
1499
- client.call([:sunionstore, destination] + keys)
1664
+ client.call([:sunionstore, destination, *keys])
1500
1665
  end
1501
1666
  end
1502
1667
 
@@ -1507,7 +1672,7 @@ class Redis
1507
1672
  # # => 4
1508
1673
  #
1509
1674
  # @param [String] key
1510
- # @return [Fixnum]
1675
+ # @return [Integer]
1511
1676
  def zcard(key)
1512
1677
  synchronize do |client|
1513
1678
  client.call([:zcard, key])
@@ -1531,6 +1696,10 @@ class Redis
1531
1696
  # add elements)
1532
1697
  # - `:nx => true`: Don't update already existing elements (always
1533
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
1534
1703
  # - `:ch => true`: Modify the return value from the number of new
1535
1704
  # elements added, to the total number of elements changed (CH is an
1536
1705
  # abbreviation of changed); changed elements are new elements added
@@ -1538,38 +1707,29 @@ class Redis
1538
1707
  # - `:incr => true`: When this option is specified ZADD acts like
1539
1708
  # ZINCRBY; only one score-element pair can be specified in this mode
1540
1709
  #
1541
- # @return [Boolean, Fixnum, Float]
1710
+ # @return [Boolean, Integer, Float]
1542
1711
  # - `Boolean` when a single pair is specified, holding whether or not it was
1543
1712
  # **added** to the sorted set.
1544
- # - `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
1545
1714
  # pairs that were **added** to the sorted set.
1546
1715
  # - `Float` when option :incr is specified, holding the score of the member
1547
1716
  # after incrementing it.
1548
- def zadd(key, *args) #, options
1549
- zadd_options = []
1550
- if args.last.is_a?(Hash)
1551
- options = args.pop
1552
-
1553
- nx = options[:nx]
1554
- zadd_options << "NX" if nx
1555
-
1556
- xx = options[:xx]
1557
- zadd_options << "XX" if xx
1558
-
1559
- ch = options[:ch]
1560
- zadd_options << "CH" if ch
1561
-
1562
- incr = options[:incr]
1563
- zadd_options << "INCR" if incr
1564
- 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
1565
1725
 
1566
1726
  synchronize do |client|
1567
1727
  if args.size == 1 && args[0].is_a?(Array)
1568
1728
  # Variadic: return float if INCR, integer if !INCR
1569
- client.call([:zadd, key] + zadd_options + args[0], &(incr ? Floatify : nil))
1729
+ client.call(command + args[0], &(incr ? Floatify : nil))
1570
1730
  elsif args.size == 2
1571
1731
  # Single pair: return float if INCR, boolean if !INCR
1572
- client.call([:zadd, key] + zadd_options + args, &(incr ? Floatify : Boolify))
1732
+ client.call(command + args, &(incr ? Floatify : Boolify))
1573
1733
  else
1574
1734
  raise ArgumentError, "wrong number of arguments"
1575
1735
  end
@@ -1604,10 +1764,10 @@ class Redis
1604
1764
  # - a single member
1605
1765
  # - an array of members
1606
1766
  #
1607
- # @return [Boolean, Fixnum]
1767
+ # @return [Boolean, Integer]
1608
1768
  # - `Boolean` when a single member is specified, holding whether or not it
1609
1769
  # was removed from the sorted set
1610
- # - `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
1611
1771
  # members that were removed to the sorted set
1612
1772
  def zrem(key, member)
1613
1773
  synchronize do |client|
@@ -1623,6 +1783,90 @@ class Redis
1623
1783
  end
1624
1784
  end
1625
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
+
1626
1870
  # Get the score associated with the given member in a sorted set.
1627
1871
  #
1628
1872
  # @example Get the score for member "a"
@@ -1638,6 +1882,63 @@ class Redis
1638
1882
  end
1639
1883
  end
1640
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
+
1641
1942
  # Return a range of members in a sorted set, by index.
1642
1943
  #
1643
1944
  # @example Retrieve all members from a sorted set
@@ -1648,18 +1949,16 @@ class Redis
1648
1949
  # # => [["a", 32.0], ["b", 64.0]]
1649
1950
  #
1650
1951
  # @param [String] key
1651
- # @param [Fixnum] start start index
1652
- # @param [Fixnum] stop stop index
1952
+ # @param [Integer] start start index
1953
+ # @param [Integer] stop stop index
1653
1954
  # @param [Hash] options
1654
1955
  # - `:with_scores => true`: include scores in output
1655
1956
  #
1656
1957
  # @return [Array<String>, Array<[String, Float]>]
1657
1958
  # - when `:with_scores` is not specified, an array of members
1658
1959
  # - when `:with_scores` is specified, an array with `[member, score]` pairs
1659
- def zrange(key, start, stop, options = {})
1660
- args = []
1661
-
1662
- with_scores = options[:with_scores] || options[:withscores]
1960
+ def zrange(key, start, stop, withscores: false, with_scores: withscores)
1961
+ args = [:zrange, key, start, stop]
1663
1962
 
1664
1963
  if with_scores
1665
1964
  args << "WITHSCORES"
@@ -1667,7 +1966,7 @@ class Redis
1667
1966
  end
1668
1967
 
1669
1968
  synchronize do |client|
1670
- client.call([:zrange, key, start, stop] + args, &block)
1969
+ client.call(args, &block)
1671
1970
  end
1672
1971
  end
1673
1972
 
@@ -1682,10 +1981,8 @@ class Redis
1682
1981
  # # => [["b", 64.0], ["a", 32.0]]
1683
1982
  #
1684
1983
  # @see #zrange
1685
- def zrevrange(key, start, stop, options = {})
1686
- args = []
1687
-
1688
- with_scores = options[:with_scores] || options[:withscores]
1984
+ def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
1985
+ args = [:zrevrange, key, start, stop]
1689
1986
 
1690
1987
  if with_scores
1691
1988
  args << "WITHSCORES"
@@ -1693,7 +1990,7 @@ class Redis
1693
1990
  end
1694
1991
 
1695
1992
  synchronize do |client|
1696
- client.call([:zrevrange, key, start, stop] + args, &block)
1993
+ client.call(args, &block)
1697
1994
  end
1698
1995
  end
1699
1996
 
@@ -1701,7 +1998,7 @@ class Redis
1701
1998
  #
1702
1999
  # @param [String] key
1703
2000
  # @param [String] member
1704
- # @return [Fixnum]
2001
+ # @return [Integer]
1705
2002
  def zrank(key, member)
1706
2003
  synchronize do |client|
1707
2004
  client.call([:zrank, key, member])
@@ -1713,7 +2010,7 @@ class Redis
1713
2010
  #
1714
2011
  # @param [String] key
1715
2012
  # @param [String] member
1716
- # @return [Fixnum]
2013
+ # @return [Integer]
1717
2014
  def zrevrank(key, member)
1718
2015
  synchronize do |client|
1719
2016
  client.call([:zrevrank, key, member])
@@ -1730,9 +2027,9 @@ class Redis
1730
2027
  # # => 5
1731
2028
  #
1732
2029
  # @param [String] key
1733
- # @param [Fixnum] start start index
1734
- # @param [Fixnum] stop stop index
1735
- # @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
1736
2033
  def zremrangebyrank(key, start, stop)
1737
2034
  synchronize do |client|
1738
2035
  client.call([:zremrangebyrank, key, start, stop])
@@ -1756,7 +2053,7 @@ class Redis
1756
2053
  # - inclusive maximum is specified by prefixing `(`
1757
2054
  # - exclusive maximum is specified by prefixing `[`
1758
2055
  #
1759
- # @return [Fixnum] number of members within the specified lexicographical range
2056
+ # @return [Integer] number of members within the specified lexicographical range
1760
2057
  def zlexcount(key, min, max)
1761
2058
  synchronize do |client|
1762
2059
  client.call([:zlexcount, key, min, max])
@@ -1784,14 +2081,16 @@ class Redis
1784
2081
  # `count` members
1785
2082
  #
1786
2083
  # @return [Array<String>, Array<[String, Float]>]
1787
- def zrangebylex(key, min, max, options = {})
1788
- args = []
2084
+ def zrangebylex(key, min, max, limit: nil)
2085
+ args = [:zrangebylex, key, min, max]
1789
2086
 
1790
- limit = options[:limit]
1791
- args.concat(["LIMIT"] + limit) if limit
2087
+ if limit
2088
+ args << "LIMIT"
2089
+ args.concat(limit)
2090
+ end
1792
2091
 
1793
2092
  synchronize do |client|
1794
- client.call([:zrangebylex, key, min, max] + args)
2093
+ client.call(args)
1795
2094
  end
1796
2095
  end
1797
2096
 
@@ -1806,14 +2105,16 @@ class Redis
1806
2105
  # # => ["abbygail", "abby"]
1807
2106
  #
1808
2107
  # @see #zrangebylex
1809
- def zrevrangebylex(key, max, min, options = {})
1810
- args = []
2108
+ def zrevrangebylex(key, max, min, limit: nil)
2109
+ args = [:zrevrangebylex, key, max, min]
1811
2110
 
1812
- limit = options[:limit]
1813
- args.concat(["LIMIT"] + limit) if limit
2111
+ if limit
2112
+ args << "LIMIT"
2113
+ args.concat(limit)
2114
+ end
1814
2115
 
1815
2116
  synchronize do |client|
1816
- client.call([:zrevrangebylex, key, max, min] + args)
2117
+ client.call(args)
1817
2118
  end
1818
2119
  end
1819
2120
 
@@ -1844,21 +2145,21 @@ class Redis
1844
2145
  # @return [Array<String>, Array<[String, Float]>]
1845
2146
  # - when `:with_scores` is not specified, an array of members
1846
2147
  # - when `:with_scores` is specified, an array with `[member, score]` pairs
1847
- def zrangebyscore(key, min, max, options = {})
1848
- args = []
1849
-
1850
- 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]
1851
2150
 
1852
2151
  if with_scores
1853
2152
  args << "WITHSCORES"
1854
2153
  block = FloatifyPairs
1855
2154
  end
1856
2155
 
1857
- limit = options[:limit]
1858
- args.concat(["LIMIT"] + limit) if limit
2156
+ if limit
2157
+ args << "LIMIT"
2158
+ args.concat(limit)
2159
+ end
1859
2160
 
1860
2161
  synchronize do |client|
1861
- client.call([:zrangebyscore, key, min, max] + args, &block)
2162
+ client.call(args, &block)
1862
2163
  end
1863
2164
  end
1864
2165
 
@@ -1876,21 +2177,21 @@ class Redis
1876
2177
  # # => [["b", 64.0], ["a", 32.0]]
1877
2178
  #
1878
2179
  # @see #zrangebyscore
1879
- def zrevrangebyscore(key, max, min, options = {})
1880
- args = []
1881
-
1882
- 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]
1883
2182
 
1884
2183
  if with_scores
1885
- args << ["WITHSCORES"]
2184
+ args << "WITHSCORES"
1886
2185
  block = FloatifyPairs
1887
2186
  end
1888
2187
 
1889
- limit = options[:limit]
1890
- args.concat(["LIMIT"] + limit) if limit
2188
+ if limit
2189
+ args << "LIMIT"
2190
+ args.concat(limit)
2191
+ end
1891
2192
 
1892
2193
  synchronize do |client|
1893
- client.call([:zrevrangebyscore, key, max, min] + args, &block)
2194
+ client.call(args, &block)
1894
2195
  end
1895
2196
  end
1896
2197
 
@@ -1910,7 +2211,7 @@ class Redis
1910
2211
  # @param [String] max
1911
2212
  # - inclusive maximum score is specified verbatim
1912
2213
  # - exclusive maximum score is specified by prefixing `(`
1913
- # @return [Fixnum] number of members that were removed
2214
+ # @return [Integer] number of members that were removed
1914
2215
  def zremrangebyscore(key, min, max)
1915
2216
  synchronize do |client|
1916
2217
  client.call([:zremrangebyscore, key, min, max])
@@ -1933,13 +2234,52 @@ class Redis
1933
2234
  # @param [String] max
1934
2235
  # - inclusive maximum score is specified verbatim
1935
2236
  # - exclusive maximum score is specified by prefixing `(`
1936
- # @return [Fixnum] number of members in within the specified range
2237
+ # @return [Integer] number of members in within the specified range
1937
2238
  def zcount(key, min, max)
1938
2239
  synchronize do |client|
1939
2240
  client.call([:zcount, key, min, max])
1940
2241
  end
1941
2242
  end
1942
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
+
1943
2283
  # Intersect multiple sorted sets and store the resulting sorted set in a new
1944
2284
  # key.
1945
2285
  #
@@ -1953,18 +2293,19 @@ class Redis
1953
2293
  # - `:weights => [Float, Float, ...]`: weights to associate with source
1954
2294
  # sorted sets
1955
2295
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1956
- # @return [Fixnum] number of elements in the resulting sorted set
1957
- def zinterstore(destination, keys, options = {})
1958
- 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]
1959
2299
 
1960
- weights = options[:weights]
1961
- args.concat(["WEIGHTS"] + weights) if weights
2300
+ if weights
2301
+ args << "WEIGHTS"
2302
+ args.concat(weights)
2303
+ end
1962
2304
 
1963
- aggregate = options[:aggregate]
1964
- args.concat(["AGGREGATE", aggregate]) if aggregate
2305
+ args << "AGGREGATE" << aggregate if aggregate
1965
2306
 
1966
2307
  synchronize do |client|
1967
- client.call([:zinterstore, destination, keys.size] + keys + args)
2308
+ client.call(args)
1968
2309
  end
1969
2310
  end
1970
2311
 
@@ -1980,40 +2321,46 @@ class Redis
1980
2321
  # - `:weights => [Float, Float, ...]`: weights to associate with source
1981
2322
  # sorted sets
1982
2323
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1983
- # @return [Fixnum] number of elements in the resulting sorted set
1984
- def zunionstore(destination, keys, options = {})
1985
- 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]
1986
2327
 
1987
- weights = options[:weights]
1988
- args.concat(["WEIGHTS"] + weights) if weights
2328
+ if weights
2329
+ args << "WEIGHTS"
2330
+ args.concat(weights)
2331
+ end
1989
2332
 
1990
- aggregate = options[:aggregate]
1991
- args.concat(["AGGREGATE", aggregate]) if aggregate
2333
+ args << "AGGREGATE" << aggregate if aggregate
1992
2334
 
1993
2335
  synchronize do |client|
1994
- client.call([:zunionstore, destination, keys.size] + keys + args)
2336
+ client.call(args)
1995
2337
  end
1996
2338
  end
1997
2339
 
1998
2340
  # Get the number of fields in a hash.
1999
2341
  #
2000
2342
  # @param [String] key
2001
- # @return [Fixnum] number of fields in the hash
2343
+ # @return [Integer] number of fields in the hash
2002
2344
  def hlen(key)
2003
2345
  synchronize do |client|
2004
2346
  client.call([:hlen, key])
2005
2347
  end
2006
2348
  end
2007
2349
 
2008
- # 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
2009
2355
  #
2010
2356
  # @param [String] key
2011
- # @param [String] field
2012
- # @param [String] value
2013
- # @return [Boolean] whether or not the field was **added** to the hash
2014
- 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
+
2015
2362
  synchronize do |client|
2016
- client.call([:hset, key, field, value], &Boolify)
2363
+ client.call([:hset, key, *attrs])
2017
2364
  end
2018
2365
  end
2019
2366
 
@@ -2102,7 +2449,7 @@ class Redis
2102
2449
  # @see #hmget
2103
2450
  def mapped_hmget(key, *fields)
2104
2451
  hmget(key, *fields) do |reply|
2105
- if reply.kind_of?(Array)
2452
+ if reply.is_a?(Array)
2106
2453
  Hash[fields.zip(reply)]
2107
2454
  else
2108
2455
  reply
@@ -2114,7 +2461,7 @@ class Redis
2114
2461
  #
2115
2462
  # @param [String] key
2116
2463
  # @param [String, Array<String>] field
2117
- # @return [Fixnum] the number of fields that were removed from the hash
2464
+ # @return [Integer] the number of fields that were removed from the hash
2118
2465
  def hdel(key, *fields)
2119
2466
  synchronize do |client|
2120
2467
  client.call([:hdel, key, *fields])
@@ -2136,8 +2483,8 @@ class Redis
2136
2483
  #
2137
2484
  # @param [String] key
2138
2485
  # @param [String] field
2139
- # @param [Fixnum] increment
2140
- # @return [Fixnum] value of the field after incrementing it
2486
+ # @param [Integer] increment
2487
+ # @return [Integer] value of the field after incrementing it
2141
2488
  def hincrby(key, field, increment)
2142
2489
  synchronize do |client|
2143
2490
  client.call([:hincrby, key, field, increment])
@@ -2195,20 +2542,21 @@ class Redis
2195
2542
 
2196
2543
  def subscribed?
2197
2544
  synchronize do |client|
2198
- client.kind_of? SubscribedClient
2545
+ client.is_a? SubscribedClient
2199
2546
  end
2200
2547
  end
2201
2548
 
2202
2549
  # Listen for messages published to the given channels.
2203
2550
  def subscribe(*channels, &block)
2204
- synchronize do |client|
2551
+ synchronize do |_client|
2205
2552
  _subscription(:subscribe, 0, channels, block)
2206
2553
  end
2207
2554
  end
2208
2555
 
2209
- # 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.
2210
2558
  def subscribe_with_timeout(timeout, *channels, &block)
2211
- synchronize do |client|
2559
+ synchronize do |_client|
2212
2560
  _subscription(:subscribe_with_timeout, timeout, channels, block)
2213
2561
  end
2214
2562
  end
@@ -2216,21 +2564,23 @@ class Redis
2216
2564
  # Stop listening for messages posted to the given channels.
2217
2565
  def unsubscribe(*channels)
2218
2566
  synchronize do |client|
2219
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2567
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
2568
+
2220
2569
  client.unsubscribe(*channels)
2221
2570
  end
2222
2571
  end
2223
2572
 
2224
2573
  # Listen for messages published to channels matching the given patterns.
2225
2574
  def psubscribe(*channels, &block)
2226
- synchronize do |client|
2575
+ synchronize do |_client|
2227
2576
  _subscription(:psubscribe, 0, channels, block)
2228
2577
  end
2229
2578
  end
2230
2579
 
2231
- # 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.
2232
2582
  def psubscribe_with_timeout(timeout, *channels, &block)
2233
- synchronize do |client|
2583
+ synchronize do |_client|
2234
2584
  _subscription(:psubscribe_with_timeout, timeout, channels, block)
2235
2585
  end
2236
2586
  end
@@ -2238,7 +2588,8 @@ class Redis
2238
2588
  # Stop listening for messages posted to channels matching the given patterns.
2239
2589
  def punsubscribe(*channels)
2240
2590
  synchronize do |client|
2241
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2591
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
2592
+
2242
2593
  client.punsubscribe(*channels)
2243
2594
  end
2244
2595
  end
@@ -2283,7 +2634,7 @@ class Redis
2283
2634
  # @see #multi
2284
2635
  def watch(*keys)
2285
2636
  synchronize do |client|
2286
- res = client.call([:watch] + keys)
2637
+ res = client.call([:watch, *keys])
2287
2638
 
2288
2639
  if block_given?
2289
2640
  begin
@@ -2313,13 +2664,13 @@ class Redis
2313
2664
  end
2314
2665
 
2315
2666
  def pipelined
2316
- synchronize do |client|
2667
+ synchronize do |prior_client|
2317
2668
  begin
2318
- original, @client = @client, Pipeline.new
2669
+ @client = Pipeline.new(prior_client)
2319
2670
  yield(self)
2320
- original.call_pipeline(@client)
2671
+ prior_client.call_pipeline(@client)
2321
2672
  ensure
2322
- @client = original
2673
+ @client = prior_client
2323
2674
  end
2324
2675
  end
2325
2676
  end
@@ -2355,17 +2706,16 @@ class Redis
2355
2706
  # @see #watch
2356
2707
  # @see #unwatch
2357
2708
  def multi
2358
- synchronize do |client|
2709
+ synchronize do |prior_client|
2359
2710
  if !block_given?
2360
- client.call([:multi])
2711
+ prior_client.call([:multi])
2361
2712
  else
2362
2713
  begin
2363
- pipeline = Pipeline::Multi.new
2364
- original, @client = @client, pipeline
2714
+ @client = Pipeline::Multi.new(prior_client)
2365
2715
  yield(self)
2366
- original.call_pipeline(pipeline)
2716
+ prior_client.call_pipeline(@client)
2367
2717
  ensure
2368
- @client = original
2718
+ @client = prior_client
2369
2719
  end
2370
2720
  end
2371
2721
  end
@@ -2512,18 +2862,13 @@ class Redis
2512
2862
  _eval(:evalsha, args)
2513
2863
  end
2514
2864
 
2515
- def _scan(command, cursor, args, options = {}, &block)
2865
+ def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
2516
2866
  # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
2517
2867
 
2518
2868
  args << cursor
2519
-
2520
- if match = options[:match]
2521
- args.concat(["MATCH", match])
2522
- end
2523
-
2524
- if count = options[:count]
2525
- args.concat(["COUNT", count])
2526
- end
2869
+ args << "MATCH" << match if match
2870
+ args << "COUNT" << count if count
2871
+ args << "TYPE" << type if type
2527
2872
 
2528
2873
  synchronize do |client|
2529
2874
  client.call([command] + args, &block)
@@ -2538,15 +2883,19 @@ class Redis
2538
2883
  # @example Retrieve a batch of keys matching a pattern
2539
2884
  # redis.scan(4, :match => "key:1?")
2540
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"]]
2541
2889
  #
2542
2890
  # @param [String, Integer] cursor the cursor of the iteration
2543
2891
  # @param [Hash] options
2544
2892
  # - `:match => String`: only return keys matching the pattern
2545
2893
  # - `:count => Integer`: return count keys at most per iteration
2894
+ # - `:type => String`: return keys only of the given type
2546
2895
  #
2547
2896
  # @return [String, Array<String>] the next cursor and all found keys
2548
- def scan(cursor, options={})
2549
- _scan(:scan, cursor, [], options)
2897
+ def scan(cursor, **options)
2898
+ _scan(:scan, cursor, [], **options)
2550
2899
  end
2551
2900
 
2552
2901
  # Scan the keyspace
@@ -2558,17 +2907,23 @@ class Redis
2558
2907
  # redis.scan_each(:match => "key:1?") {|key| puts key}
2559
2908
  # # => key:13
2560
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"
2561
2914
  #
2562
2915
  # @param [Hash] options
2563
2916
  # - `:match => String`: only return keys matching the pattern
2564
2917
  # - `:count => Integer`: return count keys at most per iteration
2918
+ # - `:type => String`: return keys only of the given type
2565
2919
  #
2566
2920
  # @return [Enumerator] an enumerator for all found keys
2567
- def scan_each(options={}, &block)
2568
- 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
+
2569
2924
  cursor = 0
2570
2925
  loop do
2571
- cursor, keys = scan(cursor, options)
2926
+ cursor, keys = scan(cursor, **options)
2572
2927
  keys.each(&block)
2573
2928
  break if cursor == "0"
2574
2929
  end
@@ -2585,8 +2940,8 @@ class Redis
2585
2940
  # - `:count => Integer`: return count keys at most per iteration
2586
2941
  #
2587
2942
  # @return [String, Array<[String, String]>] the next cursor and all found keys
2588
- def hscan(key, cursor, options={})
2589
- _scan(:hscan, cursor, [key], options) do |reply|
2943
+ def hscan(key, cursor, **options)
2944
+ _scan(:hscan, cursor, [key], **options) do |reply|
2590
2945
  [reply[0], reply[1].each_slice(2).to_a]
2591
2946
  end
2592
2947
  end
@@ -2602,11 +2957,12 @@ class Redis
2602
2957
  # - `:count => Integer`: return count keys at most per iteration
2603
2958
  #
2604
2959
  # @return [Enumerator] an enumerator for all found keys
2605
- def hscan_each(key, options={}, &block)
2606
- 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
+
2607
2963
  cursor = 0
2608
2964
  loop do
2609
- cursor, values = hscan(key, cursor, options)
2965
+ cursor, values = hscan(key, cursor, **options)
2610
2966
  values.each(&block)
2611
2967
  break if cursor == "0"
2612
2968
  end
@@ -2624,8 +2980,8 @@ class Redis
2624
2980
  #
2625
2981
  # @return [String, Array<[String, Float]>] the next cursor and all found
2626
2982
  # members and scores
2627
- def zscan(key, cursor, options={})
2628
- _scan(:zscan, cursor, [key], options) do |reply|
2983
+ def zscan(key, cursor, **options)
2984
+ _scan(:zscan, cursor, [key], **options) do |reply|
2629
2985
  [reply[0], FloatifyPairs.call(reply[1])]
2630
2986
  end
2631
2987
  end
@@ -2641,11 +2997,12 @@ class Redis
2641
2997
  # - `:count => Integer`: return count keys at most per iteration
2642
2998
  #
2643
2999
  # @return [Enumerator] an enumerator for all found scores and members
2644
- def zscan_each(key, options={}, &block)
2645
- 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
+
2646
3003
  cursor = 0
2647
3004
  loop do
2648
- cursor, values = zscan(key, cursor, options)
3005
+ cursor, values = zscan(key, cursor, **options)
2649
3006
  values.each(&block)
2650
3007
  break if cursor == "0"
2651
3008
  end
@@ -2662,8 +3019,8 @@ class Redis
2662
3019
  # - `:count => Integer`: return count keys at most per iteration
2663
3020
  #
2664
3021
  # @return [String, Array<String>] the next cursor and all found members
2665
- def sscan(key, cursor, options={})
2666
- _scan(:sscan, cursor, [key], options)
3022
+ def sscan(key, cursor, **options)
3023
+ _scan(:sscan, cursor, [key], **options)
2667
3024
  end
2668
3025
 
2669
3026
  # Scan a set
@@ -2677,11 +3034,12 @@ class Redis
2677
3034
  # - `:count => Integer`: return count keys at most per iteration
2678
3035
  #
2679
3036
  # @return [Enumerator] an enumerator for all keys in the set
2680
- def sscan_each(key, options={}, &block)
2681
- 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
+
2682
3040
  cursor = 0
2683
3041
  loop do
2684
- cursor, keys = sscan(key, cursor, options)
3042
+ cursor, keys = sscan(key, cursor, **options)
2685
3043
  keys.each(&block)
2686
3044
  break if cursor == "0"
2687
3045
  end
@@ -2704,7 +3062,7 @@ class Redis
2704
3062
  # union of the HyperLogLogs contained in the keys.
2705
3063
  #
2706
3064
  # @param [String, Array<String>] keys
2707
- # @return [Fixnum]
3065
+ # @return [Integer]
2708
3066
  def pfcount(*keys)
2709
3067
  synchronize do |client|
2710
3068
  client.call([:pfcount] + keys)
@@ -2727,10 +3085,10 @@ class Redis
2727
3085
  #
2728
3086
  # @param [String] key
2729
3087
  # @param [Array] member arguemnts for member or members: longitude, latitude, name
2730
- # @return [Intger] number of elements added to the sorted set
3088
+ # @return [Integer] number of elements added to the sorted set
2731
3089
  def geoadd(key, *member)
2732
3090
  synchronize do |client|
2733
- client.call([:geoadd, key, member])
3091
+ client.call([:geoadd, key, *member])
2734
3092
  end
2735
3093
  end
2736
3094
 
@@ -2745,12 +3103,12 @@ class Redis
2745
3103
  end
2746
3104
  end
2747
3105
 
2748
-
2749
3106
  # Query a sorted set representing a geospatial index to fetch members matching a
2750
3107
  # given maximum distance from a point
2751
3108
  #
2752
3109
  # @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
2753
- # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
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
2754
3112
  # @param [Integer] count limit the results to the first N matching items
2755
3113
  # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2756
3114
  # @return [Array<String>] may be changed with `options`
@@ -2767,7 +3125,8 @@ class Redis
2767
3125
  # given maximum distance from an already existing member
2768
3126
  #
2769
3127
  # @param [Array] args key, member, radius, unit(m|km|ft|mi)
2770
- # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
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
2771
3130
  # @param [Integer] count limit the results to the first N matching items
2772
3131
  # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2773
3132
  # @return [Array<String>] may be changed with `options`
@@ -2784,7 +3143,8 @@ class Redis
2784
3143
  #
2785
3144
  # @param [String] key
2786
3145
  # @param [String, Array<String>] member one member or array of members
2787
- # @return [Array<Array<String>, nil>] returns array of elements, where each element is either array of longitude and latitude or nil
3146
+ # @return [Array<Array<String>, nil>] returns array of elements, where each
3147
+ # element is either array of longitude and latitude or nil
2788
3148
  def geopos(key, member)
2789
3149
  synchronize do |client|
2790
3150
  client.call([:geopos, key, member])
@@ -2803,6 +3163,363 @@ class Redis
2803
3163
  end
2804
3164
  end
2805
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
+
2806
3523
  # Interact with the sentinel command (masters, master, slaves, failover)
2807
3524
  #
2808
3525
  # @param [String] subcommand e.g. `masters`, `master`, `slaves`
@@ -2816,8 +3533,8 @@ class Redis
2816
3533
  when "get-master-addr-by-name"
2817
3534
  reply
2818
3535
  else
2819
- if reply.kind_of?(Array)
2820
- if reply[0].kind_of?(Array)
3536
+ if reply.is_a?(Array)
3537
+ if reply[0].is_a?(Array)
2821
3538
  reply.map(&Hashify)
2822
3539
  else
2823
3540
  Hashify.call(reply)
@@ -2841,12 +3558,17 @@ class Redis
2841
3558
  def cluster(subcommand, *args)
2842
3559
  subcommand = subcommand.to_s.downcase
2843
3560
  block = case subcommand
2844
- when 'slots' then HashifyClusterSlots
2845
- when 'nodes' then HashifyClusterNodes
2846
- when 'slaves' then HashifyClusterSlaves
2847
- when 'info' then HashifyInfo
2848
- else Noop
2849
- end
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
2850
3572
 
2851
3573
  # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
2852
3574
  block = Noop unless @cluster_mode
@@ -2881,113 +3603,171 @@ class Redis
2881
3603
  return @original_client.connection_info if @cluster_mode
2882
3604
 
2883
3605
  {
2884
- host: @original_client.host,
2885
- port: @original_client.port,
2886
- db: @original_client.db,
2887
- id: @original_client.id,
3606
+ host: @original_client.host,
3607
+ port: @original_client.port,
3608
+ db: @original_client.db,
3609
+ id: @original_client.id,
2888
3610
  location: @original_client.location
2889
3611
  }
2890
3612
  end
2891
3613
 
2892
- def method_missing(command, *args)
3614
+ def method_missing(command, *args) # rubocop:disable Style/MissingRespondToMissing
2893
3615
  synchronize do |client|
2894
3616
  client.call([command] + args)
2895
3617
  end
2896
3618
  end
2897
3619
 
2898
- private
3620
+ private
2899
3621
 
2900
3622
  # Commands returning 1 for true and 0 for false may be executed in a pipeline
2901
3623
  # where the method call will return nil. Propagate the nil instead of falsely
2902
3624
  # returning false.
2903
- Boolify =
2904
- lambda { |value|
2905
- value == 1 if value
2906
- }
3625
+ Boolify = lambda { |value|
3626
+ case value
3627
+ when 1
3628
+ true
3629
+ when 0
3630
+ false
3631
+ else
3632
+ value
3633
+ end
3634
+ }
2907
3635
 
2908
- BoolifySet =
2909
- lambda { |value|
2910
- if value && "OK" == value
2911
- true
2912
- else
2913
- false
2914
- end
2915
- }
3636
+ BoolifySet = lambda { |value|
3637
+ case value
3638
+ when "OK"
3639
+ true
3640
+ when nil
3641
+ false
3642
+ else
3643
+ value
3644
+ end
3645
+ }
2916
3646
 
2917
- Hashify =
2918
- lambda { |array|
2919
- hash = Hash.new
2920
- array.each_slice(2) do |field, value|
2921
- hash[field] = value
2922
- end
2923
- hash
2924
- }
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
+ }
2925
3667
 
2926
- Floatify =
2927
- lambda { |str|
2928
- if str
2929
- if (inf = str.match(/^(-)?inf/i))
2930
- (inf[1] ? -1.0 : 1.0) / 0.0
2931
- else
2932
- Float(str)
2933
- end
2934
- 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] }
2935
3705
  }
3706
+ }
2936
3707
 
2937
- FloatifyPairs =
2938
- lambda { |result|
2939
- result.each_slice(2).map do |member, score|
2940
- [member, Floatify.call(score)]
2941
- end
3708
+ HashifyStreamAutoclaimJustId = lambda { |reply|
3709
+ {
3710
+ 'next' => reply[0],
3711
+ 'entries' => reply[1]
2942
3712
  }
3713
+ }
2943
3714
 
2944
- HashifyInfo =
2945
- lambda { |reply|
2946
- Hash[reply.split("\r\n").map do |line|
2947
- line.split(':', 2) unless line =~ /^(#|$)/
2948
- end.compact]
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
2949
3721
  }
3722
+ }
2950
3723
 
2951
- HashifyClusterNodeInfo =
2952
- lambda { |str|
2953
- arr = str.split(' ')
3724
+ HashifyStreamPendingDetails = lambda { |reply|
3725
+ reply.map do |arr|
2954
3726
  {
2955
- 'node_id' => arr[0],
2956
- 'ip_port' => arr[1],
2957
- 'flags' => arr[2].split(','),
2958
- 'master_node_id' => arr[3],
2959
- 'ping_sent' => arr[4],
2960
- 'pong_recv' => arr[5],
2961
- 'config_epoch' => arr[6],
2962
- 'link_state' => arr[7],
2963
- 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
3727
+ 'entry_id' => arr[0],
3728
+ 'consumer' => arr[1],
3729
+ 'elapsed' => arr[2],
3730
+ 'count' => arr[3]
2964
3731
  }
2965
- }
3732
+ end
3733
+ }
2966
3734
 
2967
- HashifyClusterSlots =
2968
- lambda { |reply|
2969
- reply.map do |arr|
2970
- first_slot, last_slot = arr[0..1]
2971
- master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
2972
- replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
2973
- {
2974
- 'start_slot' => first_slot,
2975
- 'end_slot' => last_slot,
2976
- 'master' => master,
2977
- 'replicas' => replicas
2978
- }
2979
- end
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('-'))
2980
3747
  }
3748
+ }
2981
3749
 
2982
- HashifyClusterNodes =
2983
- lambda { |reply|
2984
- reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
2985
- }
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
+ }
2986
3763
 
2987
- HashifyClusterSlaves =
2988
- lambda { |reply|
2989
- reply.map { |str| HashifyClusterNodeInfo.call(str) }
2990
- }
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
+ }
2991
3771
 
2992
3772
  Noop = ->(reply) { reply }
2993
3773
 
@@ -2995,8 +3775,7 @@ private
2995
3775
  args.push sort if sort
2996
3776
  args.push 'count', count if count
2997
3777
  args.push options if options
2998
-
2999
- args.uniq
3778
+ args
3000
3779
  end
3001
3780
 
3002
3781
  def _subscription(method, timeout, channels, block)
@@ -3013,6 +3792,40 @@ private
3013
3792
  @client = original
3014
3793
  end
3015
3794
  end
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
3016
3829
  end
3017
3830
 
3018
3831
  require_relative "redis/version"