discourse-redis 3.2.2

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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +59 -0
  4. data/.travis/Gemfile +11 -0
  5. data/.yardopts +3 -0
  6. data/CHANGELOG.md +349 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +20 -0
  9. data/README.md +328 -0
  10. data/Rakefile +87 -0
  11. data/benchmarking/logging.rb +71 -0
  12. data/benchmarking/pipeline.rb +51 -0
  13. data/benchmarking/speed.rb +21 -0
  14. data/benchmarking/suite.rb +24 -0
  15. data/benchmarking/worker.rb +71 -0
  16. data/examples/basic.rb +15 -0
  17. data/examples/consistency.rb +114 -0
  18. data/examples/dist_redis.rb +43 -0
  19. data/examples/incr-decr.rb +17 -0
  20. data/examples/list.rb +26 -0
  21. data/examples/pubsub.rb +37 -0
  22. data/examples/sentinel.rb +41 -0
  23. data/examples/sentinel/start +49 -0
  24. data/examples/sets.rb +36 -0
  25. data/examples/unicorn/config.ru +3 -0
  26. data/examples/unicorn/unicorn.rb +20 -0
  27. data/lib/redis.rb +2731 -0
  28. data/lib/redis/client.rb +575 -0
  29. data/lib/redis/connection.rb +9 -0
  30. data/lib/redis/connection/command_helper.rb +44 -0
  31. data/lib/redis/connection/hiredis.rb +64 -0
  32. data/lib/redis/connection/registry.rb +12 -0
  33. data/lib/redis/connection/ruby.rb +322 -0
  34. data/lib/redis/connection/synchrony.rb +124 -0
  35. data/lib/redis/distributed.rb +873 -0
  36. data/lib/redis/errors.rb +40 -0
  37. data/lib/redis/hash_ring.rb +132 -0
  38. data/lib/redis/pipeline.rb +141 -0
  39. data/lib/redis/subscribe.rb +83 -0
  40. data/lib/redis/version.rb +3 -0
  41. data/redis.gemspec +34 -0
  42. data/test/bitpos_test.rb +69 -0
  43. data/test/blocking_commands_test.rb +42 -0
  44. data/test/command_map_test.rb +30 -0
  45. data/test/commands_on_hashes_test.rb +21 -0
  46. data/test/commands_on_hyper_log_log_test.rb +21 -0
  47. data/test/commands_on_lists_test.rb +20 -0
  48. data/test/commands_on_sets_test.rb +77 -0
  49. data/test/commands_on_sorted_sets_test.rb +137 -0
  50. data/test/commands_on_strings_test.rb +101 -0
  51. data/test/commands_on_value_types_test.rb +133 -0
  52. data/test/connection_handling_test.rb +250 -0
  53. data/test/distributed_blocking_commands_test.rb +46 -0
  54. data/test/distributed_commands_on_hashes_test.rb +10 -0
  55. data/test/distributed_commands_on_hyper_log_log_test.rb +33 -0
  56. data/test/distributed_commands_on_lists_test.rb +22 -0
  57. data/test/distributed_commands_on_sets_test.rb +83 -0
  58. data/test/distributed_commands_on_sorted_sets_test.rb +18 -0
  59. data/test/distributed_commands_on_strings_test.rb +59 -0
  60. data/test/distributed_commands_on_value_types_test.rb +95 -0
  61. data/test/distributed_commands_requiring_clustering_test.rb +164 -0
  62. data/test/distributed_connection_handling_test.rb +23 -0
  63. data/test/distributed_internals_test.rb +79 -0
  64. data/test/distributed_key_tags_test.rb +52 -0
  65. data/test/distributed_persistence_control_commands_test.rb +26 -0
  66. data/test/distributed_publish_subscribe_test.rb +92 -0
  67. data/test/distributed_remote_server_control_commands_test.rb +66 -0
  68. data/test/distributed_scripting_test.rb +102 -0
  69. data/test/distributed_sorting_test.rb +20 -0
  70. data/test/distributed_test.rb +58 -0
  71. data/test/distributed_transactions_test.rb +32 -0
  72. data/test/encoding_test.rb +18 -0
  73. data/test/error_replies_test.rb +59 -0
  74. data/test/fork_safety_test.rb +65 -0
  75. data/test/helper.rb +232 -0
  76. data/test/helper_test.rb +24 -0
  77. data/test/internals_test.rb +437 -0
  78. data/test/lint/blocking_commands.rb +150 -0
  79. data/test/lint/hashes.rb +162 -0
  80. data/test/lint/hyper_log_log.rb +60 -0
  81. data/test/lint/lists.rb +143 -0
  82. data/test/lint/sets.rb +125 -0
  83. data/test/lint/sorted_sets.rb +316 -0
  84. data/test/lint/strings.rb +260 -0
  85. data/test/lint/value_types.rb +122 -0
  86. data/test/persistence_control_commands_test.rb +26 -0
  87. data/test/pipelining_commands_test.rb +242 -0
  88. data/test/publish_subscribe_test.rb +254 -0
  89. data/test/remote_server_control_commands_test.rb +118 -0
  90. data/test/scanning_test.rb +413 -0
  91. data/test/scripting_test.rb +78 -0
  92. data/test/sentinel_command_test.rb +80 -0
  93. data/test/sentinel_test.rb +255 -0
  94. data/test/sorting_test.rb +59 -0
  95. data/test/support/connection/hiredis.rb +1 -0
  96. data/test/support/connection/ruby.rb +1 -0
  97. data/test/support/connection/synchrony.rb +17 -0
  98. data/test/support/redis_mock.rb +119 -0
  99. data/test/support/wire/synchrony.rb +24 -0
  100. data/test/support/wire/thread.rb +5 -0
  101. data/test/synchrony_driver.rb +88 -0
  102. data/test/test.conf.erb +9 -0
  103. data/test/thread_safety_test.rb +32 -0
  104. data/test/transactions_test.rb +264 -0
  105. data/test/unknown_commands_test.rb +14 -0
  106. data/test/url_param_test.rb +138 -0
  107. metadata +182 -0
@@ -0,0 +1,49 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # This is a helper script used together with examples/sentinel.rb
4
+ # It runs two Redis masters, two slaves for each of them, and two sentinels.
5
+ # After 30 seconds, the first master dies.
6
+ #
7
+ # You don't need to run this script yourself. Rather, use examples/sentinel.rb.
8
+
9
+ require "fileutils"
10
+
11
+ $pids = []
12
+
13
+ at_exit do
14
+ $pids.each do |pid|
15
+ begin
16
+ Process.kill(:INT, pid)
17
+ rescue Errno::ESRCH
18
+ end
19
+ end
20
+
21
+ Process.waitall
22
+ end
23
+
24
+ base = File.expand_path(File.dirname(__FILE__))
25
+
26
+ # Masters
27
+ $pids << spawn("redis-server --port 6380 --loglevel warning")
28
+ $pids << spawn("redis-server --port 6381 --loglevel warning")
29
+
30
+ # Slaves of Master 1
31
+ $pids << spawn("redis-server --port 63800 --slaveof 127.0.0.1 6380 --loglevel warning")
32
+ $pids << spawn("redis-server --port 63801 --slaveof 127.0.0.1 6380 --loglevel warning")
33
+
34
+ # Slaves of Master 2
35
+ $pids << spawn("redis-server --port 63810 --slaveof 127.0.0.1 6381 --loglevel warning")
36
+ $pids << spawn("redis-server --port 63811 --slaveof 127.0.0.1 6381 --loglevel warning")
37
+
38
+ FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel1.conf")
39
+ FileUtils.cp(File.join(base, "sentinel.conf"), "tmp/sentinel2.conf")
40
+
41
+ # Sentinels
42
+ $pids << spawn("redis-server tmp/sentinel1.conf --sentinel --port 26379")
43
+ $pids << spawn("redis-server tmp/sentinel2.conf --sentinel --port 26380")
44
+
45
+ sleep 30
46
+
47
+ Process.kill(:KILL, $pids[0])
48
+
49
+ Process.waitall
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ r = Redis.new
5
+
6
+ r.del 'foo-tags'
7
+ r.del 'bar-tags'
8
+
9
+ puts
10
+ p "create a set of tags on foo-tags"
11
+
12
+ r.sadd 'foo-tags', 'one'
13
+ r.sadd 'foo-tags', 'two'
14
+ r.sadd 'foo-tags', 'three'
15
+
16
+ puts
17
+ p "create a set of tags on bar-tags"
18
+
19
+ r.sadd 'bar-tags', 'three'
20
+ r.sadd 'bar-tags', 'four'
21
+ r.sadd 'bar-tags', 'five'
22
+
23
+ puts
24
+ p 'foo-tags'
25
+
26
+ p r.smembers('foo-tags')
27
+
28
+ puts
29
+ p 'bar-tags'
30
+
31
+ p r.smembers('bar-tags')
32
+
33
+ puts
34
+ p 'intersection of foo-tags and bar-tags'
35
+
36
+ p r.sinter('foo-tags', 'bar-tags')
@@ -0,0 +1,3 @@
1
+ run lambda { |env|
2
+ [200, {"Content-Type" => "text/plain"}, [Redis.current.randomkey]]
3
+ }
@@ -0,0 +1,20 @@
1
+ require "redis"
2
+
3
+ worker_processes 3
4
+
5
+ # If you set the connection to Redis *before* forking,
6
+ # you will cause forks to share a file descriptor.
7
+ #
8
+ # This causes a concurrency problem by which one fork
9
+ # can read or write to the socket while others are
10
+ # performing other operations.
11
+ #
12
+ # Most likely you'll be getting ProtocolError exceptions
13
+ # mentioning a wrong initial byte in the reply.
14
+ #
15
+ # Thus we need to connect to Redis after forking the
16
+ # worker processes.
17
+
18
+ after_fork do |server, worker|
19
+ Redis.current.disconnect!
20
+ end
@@ -0,0 +1,2731 @@
1
+ require "monitor"
2
+ require "redis/errors"
3
+
4
+ class Redis
5
+
6
+ def self.deprecate(message, trace = caller[0])
7
+ $stderr.puts "\n#{message} (in #{trace})"
8
+ end
9
+
10
+ attr :client
11
+
12
+ # @deprecated The preferred way to create a new client object is using `#new`.
13
+ # This method does not actually establish a connection to Redis,
14
+ # in contrary to what you might expect.
15
+ def self.connect(options = {})
16
+ new(options)
17
+ end
18
+
19
+ def self.current
20
+ @current ||= Redis.new
21
+ end
22
+
23
+ def self.current=(redis)
24
+ @current = redis
25
+ end
26
+
27
+ include MonitorMixin
28
+
29
+ # Create a new client instance
30
+ #
31
+ # @param [Hash] options
32
+ # @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.
33
+ # @option options [String] :host ("127.0.0.1") server hostname
34
+ # @option options [Fixnum] :port (6379) server port
35
+ # @option options [String] :path path to server socket (overrides host and port)
36
+ # @option options [Float] :timeout (5.0) timeout in seconds
37
+ # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
38
+ # @option options [String] :password Password to authenticate against server
39
+ # @option options [Fixnum] :db (0) Database to select after initial connect
40
+ # @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`, `:synchrony`
41
+ # @option options [String] :id ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME`
42
+ # @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
43
+ # @option options [Fixnum] :reconnect_attempts Number of attempts trying to connect
44
+ # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
45
+ # @option options [Array] :sentinels List of sentinels to contact
46
+ # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
47
+ # @option options [Class] :connector Class of custom connector
48
+ #
49
+ # @return [Redis] a new client instance
50
+ def initialize(options = {})
51
+ @options = options.dup
52
+ @original_client = @client = Client.new(options)
53
+
54
+ super() # Monitor#initialize
55
+ end
56
+
57
+ def synchronize
58
+ mon_synchronize { yield(@client) }
59
+ end
60
+
61
+ # Run code with the client reconnecting
62
+ def with_reconnect(val=true, &blk)
63
+ synchronize do |client|
64
+ client.with_reconnect(val, &blk)
65
+ end
66
+ end
67
+
68
+ # Run code without the client reconnecting
69
+ def without_reconnect(&blk)
70
+ with_reconnect(false, &blk)
71
+ end
72
+
73
+ # Test whether or not the client is connected
74
+ def connected?
75
+ @original_client.connected?
76
+ end
77
+
78
+ # Disconnect the client as quickly and silently as possible.
79
+ def disconnect!
80
+ @original_client.disconnect
81
+ end
82
+
83
+ # Authenticate to the server.
84
+ #
85
+ # @param [String] password must match the password specified in the
86
+ # `requirepass` directive in the configuration file
87
+ # @return [String] `OK`
88
+ def auth(password)
89
+ synchronize do |client|
90
+ client.call([:auth, password])
91
+ end
92
+ end
93
+
94
+ # Change the selected database for the current connection.
95
+ #
96
+ # @param [Fixnum] db zero-based index of the DB to use (0 to 15)
97
+ # @return [String] `OK`
98
+ def select(db)
99
+ synchronize do |client|
100
+ client.db = db
101
+ client.call([:select, db])
102
+ end
103
+ end
104
+
105
+ # Ping the server.
106
+ #
107
+ # @return [String] `PONG`
108
+ def ping
109
+ synchronize do |client|
110
+ client.call([:ping])
111
+ end
112
+ end
113
+
114
+ # Echo the given string.
115
+ #
116
+ # @param [String] value
117
+ # @return [String]
118
+ def echo(value)
119
+ synchronize do |client|
120
+ client.call([:echo, value])
121
+ end
122
+ end
123
+
124
+ # Close the connection.
125
+ #
126
+ # @return [String] `OK`
127
+ def quit
128
+ synchronize do |client|
129
+ begin
130
+ client.call([:quit])
131
+ rescue ConnectionError
132
+ ensure
133
+ client.disconnect
134
+ end
135
+ end
136
+ end
137
+
138
+ # Asynchronously rewrite the append-only file.
139
+ #
140
+ # @return [String] `OK`
141
+ def bgrewriteaof
142
+ synchronize do |client|
143
+ client.call([:bgrewriteaof])
144
+ end
145
+ end
146
+
147
+ # Asynchronously save the dataset to disk.
148
+ #
149
+ # @return [String] `OK`
150
+ def bgsave
151
+ synchronize do |client|
152
+ client.call([:bgsave])
153
+ end
154
+ end
155
+
156
+ # Get or set server configuration parameters.
157
+ #
158
+ # @param [Symbol] action e.g. `:get`, `:set`, `:resetstat`
159
+ # @return [String, Hash] string reply, or hash when retrieving more than one
160
+ # property with `CONFIG GET`
161
+ def config(action, *args)
162
+ synchronize do |client|
163
+ client.call([:config, action] + args) do |reply|
164
+ if reply.kind_of?(Array) && action == :get
165
+ Hash[_pairify(reply)]
166
+ else
167
+ reply
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ # Return the number of keys in the selected database.
174
+ #
175
+ # @return [Fixnum]
176
+ def dbsize
177
+ synchronize do |client|
178
+ client.call([:dbsize])
179
+ end
180
+ end
181
+
182
+ def debug(*args)
183
+ synchronize do |client|
184
+ client.call([:debug] + args)
185
+ end
186
+ end
187
+
188
+ # Remove all keys from all databases.
189
+ #
190
+ # @return [String] `OK`
191
+ def flushall
192
+ synchronize do |client|
193
+ client.call([:flushall])
194
+ end
195
+ end
196
+
197
+ # Remove all keys from the current database.
198
+ #
199
+ # @return [String] `OK`
200
+ def flushdb
201
+ synchronize do |client|
202
+ client.call([:flushdb])
203
+ end
204
+ end
205
+
206
+ # Get information and statistics about the server.
207
+ #
208
+ # @param [String, Symbol] cmd e.g. "commandstats"
209
+ # @return [Hash<String, String>]
210
+ def info(cmd = nil)
211
+ synchronize do |client|
212
+ client.call([:info, cmd].compact) do |reply|
213
+ if reply.kind_of?(String)
214
+ reply = Hash[reply.split("\r\n").map do |line|
215
+ line.split(":", 2) unless line =~ /^(#|$)/
216
+ end.compact]
217
+
218
+ if cmd && cmd.to_s == "commandstats"
219
+ # Extract nested hashes for INFO COMMANDSTATS
220
+ reply = Hash[reply.map do |k, v|
221
+ v = v.split(",").map { |e| e.split("=") }
222
+ [k[/^cmdstat_(.*)$/, 1], Hash[v]]
223
+ end]
224
+ end
225
+ end
226
+
227
+ reply
228
+ end
229
+ end
230
+ end
231
+
232
+ # Get the UNIX time stamp of the last successful save to disk.
233
+ #
234
+ # @return [Fixnum]
235
+ def lastsave
236
+ synchronize do |client|
237
+ client.call([:lastsave])
238
+ end
239
+ end
240
+
241
+ # Listen for all requests received by the server in real time.
242
+ #
243
+ # There is no way to interrupt this command.
244
+ #
245
+ # @yield a block to be called for every line of output
246
+ # @yieldparam [String] line timestamp and command that was executed
247
+ def monitor(&block)
248
+ synchronize do |client|
249
+ client.call_loop([:monitor], &block)
250
+ end
251
+ end
252
+
253
+ # Synchronously save the dataset to disk.
254
+ #
255
+ # @return [String]
256
+ def save
257
+ synchronize do |client|
258
+ client.call([:save])
259
+ end
260
+ end
261
+
262
+ # Synchronously save the dataset to disk and then shut down the server.
263
+ def shutdown
264
+ synchronize do |client|
265
+ client.with_reconnect(false) do
266
+ begin
267
+ client.call([:shutdown])
268
+ rescue ConnectionError
269
+ # This means Redis has probably exited.
270
+ nil
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ # Make the server a slave of another instance, or promote it as master.
277
+ def slaveof(host, port)
278
+ synchronize do |client|
279
+ client.call([:slaveof, host, port])
280
+ end
281
+ end
282
+
283
+ # Interact with the slowlog (get, len, reset)
284
+ #
285
+ # @param [String] subcommand e.g. `get`, `len`, `reset`
286
+ # @param [Fixnum] length maximum number of entries to return
287
+ # @return [Array<String>, Fixnum, String] depends on subcommand
288
+ def slowlog(subcommand, length=nil)
289
+ synchronize do |client|
290
+ args = [:slowlog, subcommand]
291
+ args << length if length
292
+ client.call args
293
+ end
294
+ end
295
+
296
+ # Internal command used for replication.
297
+ def sync
298
+ synchronize do |client|
299
+ client.call([:sync])
300
+ end
301
+ end
302
+
303
+ # Return the server time.
304
+ #
305
+ # @example
306
+ # r.time # => [ 1333093196, 606806 ]
307
+ #
308
+ # @return [Array<Fixnum>] tuple of seconds since UNIX epoch and
309
+ # microseconds in the current second
310
+ def time
311
+ synchronize do |client|
312
+ client.call([:time]) do |reply|
313
+ reply.map(&:to_i) if reply
314
+ end
315
+ end
316
+ end
317
+
318
+ # Remove the expiration from a key.
319
+ #
320
+ # @param [String] key
321
+ # @return [Boolean] whether the timeout was removed or not
322
+ def persist(key)
323
+ synchronize do |client|
324
+ client.call([:persist, key], &_boolify)
325
+ end
326
+ end
327
+
328
+ # Set a key's time to live in seconds.
329
+ #
330
+ # @param [String] key
331
+ # @param [Fixnum] seconds time to live
332
+ # @return [Boolean] whether the timeout was set or not
333
+ def expire(key, seconds)
334
+ synchronize do |client|
335
+ client.call([:expire, key, seconds], &_boolify)
336
+ end
337
+ end
338
+
339
+ # Set the expiration for a key as a UNIX timestamp.
340
+ #
341
+ # @param [String] key
342
+ # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp
343
+ # @return [Boolean] whether the timeout was set or not
344
+ def expireat(key, unix_time)
345
+ synchronize do |client|
346
+ client.call([:expireat, key, unix_time], &_boolify)
347
+ end
348
+ end
349
+
350
+ # Get the time to live (in seconds) for a key.
351
+ #
352
+ # @param [String] key
353
+ # @return [Fixnum] remaining time to live in seconds.
354
+ #
355
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
356
+ # the key exist but has no associated expire.
357
+ #
358
+ # Starting with Redis 2.8 the return value in case of error changed:
359
+ #
360
+ # - The command returns -2 if the key does not exist.
361
+ # - The command returns -1 if the key exists but has no associated expire.
362
+ def ttl(key)
363
+ synchronize do |client|
364
+ client.call([:ttl, key])
365
+ end
366
+ end
367
+
368
+ # Set a key's time to live in milliseconds.
369
+ #
370
+ # @param [String] key
371
+ # @param [Fixnum] milliseconds time to live
372
+ # @return [Boolean] whether the timeout was set or not
373
+ def pexpire(key, milliseconds)
374
+ synchronize do |client|
375
+ client.call([:pexpire, key, milliseconds], &_boolify)
376
+ end
377
+ end
378
+
379
+ # Set the expiration for a key as number of milliseconds from UNIX Epoch.
380
+ #
381
+ # @param [String] key
382
+ # @param [Fixnum] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
383
+ # @return [Boolean] whether the timeout was set or not
384
+ def pexpireat(key, ms_unix_time)
385
+ synchronize do |client|
386
+ client.call([:pexpireat, key, ms_unix_time], &_boolify)
387
+ end
388
+ end
389
+
390
+ # Get the time to live (in milliseconds) for a key.
391
+ #
392
+ # @param [String] key
393
+ # @return [Fixnum] remaining time to live in milliseconds
394
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
395
+ # the key exist but has no associated expire.
396
+ #
397
+ # Starting with Redis 2.8 the return value in case of error changed:
398
+ #
399
+ # - The command returns -2 if the key does not exist.
400
+ # - The command returns -1 if the key exists but has no associated expire.
401
+ def pttl(key)
402
+ synchronize do |client|
403
+ client.call([:pttl, key])
404
+ end
405
+ end
406
+
407
+ # Return a serialized version of the value stored at a key.
408
+ #
409
+ # @param [String] key
410
+ # @return [String] serialized_value
411
+ def dump(key)
412
+ synchronize do |client|
413
+ client.call([:dump, key])
414
+ end
415
+ end
416
+
417
+ # Create a key using the serialized value, previously obtained using DUMP.
418
+ #
419
+ # @param [String] key
420
+ # @param [String] ttl
421
+ # @param [String] serialized_value
422
+ # @return [String] `"OK"`
423
+ def restore(key, ttl, serialized_value)
424
+ synchronize do |client|
425
+ client.call([:restore, key, ttl, serialized_value])
426
+ end
427
+ end
428
+
429
+ # Transfer a key from the connected instance to another instance.
430
+ #
431
+ # @param [String] key
432
+ # @param [Hash] options
433
+ # - `:host => String`: host of instance to migrate to
434
+ # - `:port => Integer`: port of instance to migrate to
435
+ # - `:db => Integer`: database to migrate to (default: same as source)
436
+ # - `:timeout => Integer`: timeout (default: same as connection timeout)
437
+ # @return [String] `"OK"`
438
+ def migrate(key, options)
439
+ host = options[:host] || raise(RuntimeError, ":host not specified")
440
+ port = options[:port] || raise(RuntimeError, ":port not specified")
441
+ db = (options[:db] || client.db).to_i
442
+ timeout = (options[:timeout] || client.timeout).to_i
443
+
444
+ synchronize do |client|
445
+ client.call([:migrate, host, port, key, db, timeout])
446
+ end
447
+ end
448
+
449
+ # Delete one or more keys.
450
+ #
451
+ # @param [String, Array<String>] keys
452
+ # @return [Fixnum] number of keys that were deleted
453
+ def del(*keys)
454
+ synchronize do |client|
455
+ client.call([:del] + keys)
456
+ end
457
+ end
458
+
459
+ # Determine if a key exists.
460
+ #
461
+ # @param [String] key
462
+ # @return [Boolean]
463
+ def exists(key)
464
+ synchronize do |client|
465
+ client.call([:exists, key], &_boolify)
466
+ end
467
+ end
468
+
469
+ # Find all keys matching the given pattern.
470
+ #
471
+ # @param [String] pattern
472
+ # @return [Array<String>]
473
+ def keys(pattern = "*")
474
+ synchronize do |client|
475
+ client.call([:keys, pattern]) do |reply|
476
+ if reply.kind_of?(String)
477
+ reply.split(" ")
478
+ else
479
+ reply
480
+ end
481
+ end
482
+ end
483
+ end
484
+
485
+ # Move a key to another database.
486
+ #
487
+ # @example Move a key to another database
488
+ # redis.set "foo", "bar"
489
+ # # => "OK"
490
+ # redis.move "foo", 2
491
+ # # => true
492
+ # redis.exists "foo"
493
+ # # => false
494
+ # redis.select 2
495
+ # # => "OK"
496
+ # redis.exists "foo"
497
+ # # => true
498
+ # redis.get "foo"
499
+ # # => "bar"
500
+ #
501
+ # @param [String] key
502
+ # @param [Fixnum] db
503
+ # @return [Boolean] whether the key was moved or not
504
+ def move(key, db)
505
+ synchronize do |client|
506
+ client.call([:move, key, db], &_boolify)
507
+ end
508
+ end
509
+
510
+ def object(*args)
511
+ synchronize do |client|
512
+ client.call([:object] + args)
513
+ end
514
+ end
515
+
516
+ # Return a random key from the keyspace.
517
+ #
518
+ # @return [String]
519
+ def randomkey
520
+ synchronize do |client|
521
+ client.call([:randomkey])
522
+ end
523
+ end
524
+
525
+ # Rename a key. If the new key already exists it is overwritten.
526
+ #
527
+ # @param [String] old_name
528
+ # @param [String] new_name
529
+ # @return [String] `OK`
530
+ def rename(old_name, new_name)
531
+ synchronize do |client|
532
+ client.call([:rename, old_name, new_name])
533
+ end
534
+ end
535
+
536
+ # Rename a key, only if the new key does not exist.
537
+ #
538
+ # @param [String] old_name
539
+ # @param [String] new_name
540
+ # @return [Boolean] whether the key was renamed or not
541
+ def renamenx(old_name, new_name)
542
+ synchronize do |client|
543
+ client.call([:renamenx, old_name, new_name], &_boolify)
544
+ end
545
+ end
546
+
547
+ # Sort the elements in a list, set or sorted set.
548
+ #
549
+ # @example Retrieve the first 2 elements from an alphabetically sorted "list"
550
+ # redis.sort("list", :order => "alpha", :limit => [0, 2])
551
+ # # => ["a", "b"]
552
+ # @example Store an alphabetically descending list in "target"
553
+ # redis.sort("list", :order => "desc alpha", :store => "target")
554
+ # # => 26
555
+ #
556
+ # @param [String] key
557
+ # @param [Hash] options
558
+ # - `:by => String`: use external key to sort elements by
559
+ # - `:limit => [offset, count]`: skip `offset` elements, return a maximum
560
+ # of `count` elements
561
+ # - `:get => [String, Array<String>]`: single key or array of keys to
562
+ # retrieve per element in the result
563
+ # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
564
+ # - `:store => String`: key to store the result at
565
+ #
566
+ # @return [Array<String>, Array<Array<String>>, Fixnum]
567
+ # - when `:get` is not specified, or holds a single element, an array of elements
568
+ # - when `:get` is specified, and holds more than one element, an array of
569
+ # elements where every element is an array with the result for every
570
+ # element specified in `:get`
571
+ # - when `:store` is specified, the number of elements in the stored result
572
+ def sort(key, options = {})
573
+ args = []
574
+
575
+ by = options[:by]
576
+ args.concat(["BY", by]) if by
577
+
578
+ limit = options[:limit]
579
+ args.concat(["LIMIT"] + limit) if limit
580
+
581
+ get = Array(options[:get])
582
+ args.concat(["GET"].product(get).flatten) unless get.empty?
583
+
584
+ order = options[:order]
585
+ args.concat(order.split(" ")) if order
586
+
587
+ store = options[:store]
588
+ args.concat(["STORE", store]) if store
589
+
590
+ synchronize do |client|
591
+ client.call([:sort, key] + args) do |reply|
592
+ if get.size > 1 && !store
593
+ if reply
594
+ reply.each_slice(get.size).to_a
595
+ end
596
+ else
597
+ reply
598
+ end
599
+ end
600
+ end
601
+ end
602
+
603
+ # Determine the type stored at key.
604
+ #
605
+ # @param [String] key
606
+ # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
607
+ def type(key)
608
+ synchronize do |client|
609
+ client.call([:type, key])
610
+ end
611
+ end
612
+
613
+ # Decrement the integer value of a key by one.
614
+ #
615
+ # @example
616
+ # redis.decr("value")
617
+ # # => 4
618
+ #
619
+ # @param [String] key
620
+ # @return [Fixnum] value after decrementing it
621
+ def decr(key)
622
+ synchronize do |client|
623
+ client.call([:decr, key])
624
+ end
625
+ end
626
+
627
+ # Decrement the integer value of a key by the given number.
628
+ #
629
+ # @example
630
+ # redis.decrby("value", 5)
631
+ # # => 0
632
+ #
633
+ # @param [String] key
634
+ # @param [Fixnum] decrement
635
+ # @return [Fixnum] value after decrementing it
636
+ def decrby(key, decrement)
637
+ synchronize do |client|
638
+ client.call([:decrby, key, decrement])
639
+ end
640
+ end
641
+
642
+ # Increment the integer value of a key by one.
643
+ #
644
+ # @example
645
+ # redis.incr("value")
646
+ # # => 6
647
+ #
648
+ # @param [String] key
649
+ # @return [Fixnum] value after incrementing it
650
+ def incr(key)
651
+ synchronize do |client|
652
+ client.call([:incr, key])
653
+ end
654
+ end
655
+
656
+ # Increment the integer value of a key by the given integer number.
657
+ #
658
+ # @example
659
+ # redis.incrby("value", 5)
660
+ # # => 10
661
+ #
662
+ # @param [String] key
663
+ # @param [Fixnum] increment
664
+ # @return [Fixnum] value after incrementing it
665
+ def incrby(key, increment)
666
+ synchronize do |client|
667
+ client.call([:incrby, key, increment])
668
+ end
669
+ end
670
+
671
+ # Increment the numeric value of a key by the given float number.
672
+ #
673
+ # @example
674
+ # redis.incrbyfloat("value", 1.23)
675
+ # # => 1.23
676
+ #
677
+ # @param [String] key
678
+ # @param [Float] increment
679
+ # @return [Float] value after incrementing it
680
+ def incrbyfloat(key, increment)
681
+ synchronize do |client|
682
+ client.call([:incrbyfloat, key, increment], &_floatify)
683
+ end
684
+ end
685
+
686
+ # Set the string value of a key.
687
+ #
688
+ # @param [String] key
689
+ # @param [String] value
690
+ # @param [Hash] options
691
+ # - `:ex => Fixnum`: Set the specified expire time, in seconds.
692
+ # - `:px => Fixnum`: Set the specified expire time, in milliseconds.
693
+ # - `:nx => true`: Only set the key if it does not already exist.
694
+ # - `:xx => true`: Only set the key if it already exist.
695
+ # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
696
+ def set(key, value, options = {})
697
+ args = []
698
+
699
+ ex = options[:ex]
700
+ args.concat(["EX", ex]) if ex
701
+
702
+ px = options[:px]
703
+ args.concat(["PX", px]) if px
704
+
705
+ nx = options[:nx]
706
+ args.concat(["NX"]) if nx
707
+
708
+ xx = options[:xx]
709
+ args.concat(["XX"]) if xx
710
+
711
+ synchronize do |client|
712
+ if nx || xx
713
+ client.call([:set, key, value.to_s] + args, &_boolify_set)
714
+ else
715
+ client.call([:set, key, value.to_s] + args)
716
+ end
717
+ end
718
+ end
719
+
720
+ alias :[]= :set
721
+
722
+ # Set the time to live in seconds of a key.
723
+ #
724
+ # @param [String] key
725
+ # @param [Fixnum] ttl
726
+ # @param [String] value
727
+ # @return [String] `"OK"`
728
+ def setex(key, ttl, value)
729
+ synchronize do |client|
730
+ client.call([:setex, key, ttl, value.to_s])
731
+ end
732
+ end
733
+
734
+ # Set the time to live in milliseconds of a key.
735
+ #
736
+ # @param [String] key
737
+ # @param [Fixnum] ttl
738
+ # @param [String] value
739
+ # @return [String] `"OK"`
740
+ def psetex(key, ttl, value)
741
+ synchronize do |client|
742
+ client.call([:psetex, key, ttl, value.to_s])
743
+ end
744
+ end
745
+
746
+ # Set the value of a key, only if the key does not exist.
747
+ #
748
+ # @param [String] key
749
+ # @param [String] value
750
+ # @return [Boolean] whether the key was set or not
751
+ def setnx(key, value)
752
+ synchronize do |client|
753
+ client.call([:setnx, key, value.to_s], &_boolify)
754
+ end
755
+ end
756
+
757
+ # Set one or more values.
758
+ #
759
+ # @example
760
+ # redis.mset("key1", "v1", "key2", "v2")
761
+ # # => "OK"
762
+ #
763
+ # @param [Array<String>] args array of keys and values
764
+ # @return [String] `"OK"`
765
+ #
766
+ # @see #mapped_mset
767
+ def mset(*args)
768
+ synchronize do |client|
769
+ client.call([:mset] + args)
770
+ end
771
+ end
772
+
773
+ # Set one or more values.
774
+ #
775
+ # @example
776
+ # redis.mapped_mset({ "f1" => "v1", "f2" => "v2" })
777
+ # # => "OK"
778
+ #
779
+ # @param [Hash] hash keys mapping to values
780
+ # @return [String] `"OK"`
781
+ #
782
+ # @see #mset
783
+ def mapped_mset(hash)
784
+ mset(hash.to_a.flatten)
785
+ end
786
+
787
+ # Set one or more values, only if none of the keys exist.
788
+ #
789
+ # @example
790
+ # redis.msetnx("key1", "v1", "key2", "v2")
791
+ # # => true
792
+ #
793
+ # @param [Array<String>] args array of keys and values
794
+ # @return [Boolean] whether or not all values were set
795
+ #
796
+ # @see #mapped_msetnx
797
+ def msetnx(*args)
798
+ synchronize do |client|
799
+ client.call([:msetnx] + args, &_boolify)
800
+ end
801
+ end
802
+
803
+ # Set one or more values, only if none of the keys exist.
804
+ #
805
+ # @example
806
+ # redis.mapped_msetnx({ "key1" => "v1", "key2" => "v2" })
807
+ # # => true
808
+ #
809
+ # @param [Hash] hash keys mapping to values
810
+ # @return [Boolean] whether or not all values were set
811
+ #
812
+ # @see #msetnx
813
+ def mapped_msetnx(hash)
814
+ msetnx(hash.to_a.flatten)
815
+ end
816
+
817
+ # Get the value of a key.
818
+ #
819
+ # @param [String] key
820
+ # @return [String]
821
+ def get(key)
822
+ synchronize do |client|
823
+ client.call([:get, key])
824
+ end
825
+ end
826
+
827
+ alias :[] :get
828
+
829
+ # Get the values of all the given keys.
830
+ #
831
+ # @example
832
+ # redis.mget("key1", "key1")
833
+ # # => ["v1", "v2"]
834
+ #
835
+ # @param [Array<String>] keys
836
+ # @return [Array<String>] an array of values for the specified keys
837
+ #
838
+ # @see #mapped_mget
839
+ def mget(*keys, &blk)
840
+ synchronize do |client|
841
+ client.call([:mget] + keys, &blk)
842
+ end
843
+ end
844
+
845
+ # Get the values of all the given keys.
846
+ #
847
+ # @example
848
+ # redis.mapped_mget("key1", "key1")
849
+ # # => { "key1" => "v1", "key2" => "v2" }
850
+ #
851
+ # @param [Array<String>] keys array of keys
852
+ # @return [Hash] a hash mapping the specified keys to their values
853
+ #
854
+ # @see #mget
855
+ def mapped_mget(*keys)
856
+ mget(*keys) do |reply|
857
+ if reply.kind_of?(Array)
858
+ Hash[keys.zip(reply)]
859
+ else
860
+ reply
861
+ end
862
+ end
863
+ end
864
+
865
+ # Overwrite part of a string at key starting at the specified offset.
866
+ #
867
+ # @param [String] key
868
+ # @param [Fixnum] offset byte offset
869
+ # @param [String] value
870
+ # @return [Fixnum] length of the string after it was modified
871
+ def setrange(key, offset, value)
872
+ synchronize do |client|
873
+ client.call([:setrange, key, offset, value.to_s])
874
+ end
875
+ end
876
+
877
+ # Get a substring of the string stored at a key.
878
+ #
879
+ # @param [String] key
880
+ # @param [Fixnum] start zero-based start offset
881
+ # @param [Fixnum] stop zero-based end offset. Use -1 for representing
882
+ # the end of the string
883
+ # @return [Fixnum] `0` or `1`
884
+ def getrange(key, start, stop)
885
+ synchronize do |client|
886
+ client.call([:getrange, key, start, stop])
887
+ end
888
+ end
889
+
890
+ # Sets or clears the bit at offset in the string value stored at key.
891
+ #
892
+ # @param [String] key
893
+ # @param [Fixnum] offset bit offset
894
+ # @param [Fixnum] value bit value `0` or `1`
895
+ # @return [Fixnum] the original bit value stored at `offset`
896
+ def setbit(key, offset, value)
897
+ synchronize do |client|
898
+ client.call([:setbit, key, offset, value])
899
+ end
900
+ end
901
+
902
+ # Returns the bit value at offset in the string value stored at key.
903
+ #
904
+ # @param [String] key
905
+ # @param [Fixnum] offset bit offset
906
+ # @return [Fixnum] `0` or `1`
907
+ def getbit(key, offset)
908
+ synchronize do |client|
909
+ client.call([:getbit, key, offset])
910
+ end
911
+ end
912
+
913
+ # Append a value to a key.
914
+ #
915
+ # @param [String] key
916
+ # @param [String] value value to append
917
+ # @return [Fixnum] length of the string after appending
918
+ def append(key, value)
919
+ synchronize do |client|
920
+ client.call([:append, key, value])
921
+ end
922
+ end
923
+
924
+ # Count the number of set bits in a range of the string value stored at key.
925
+ #
926
+ # @param [String] key
927
+ # @param [Fixnum] start start index
928
+ # @param [Fixnum] stop stop index
929
+ # @return [Fixnum] the number of bits set to 1
930
+ def bitcount(key, start = 0, stop = -1)
931
+ synchronize do |client|
932
+ client.call([:bitcount, key, start, stop])
933
+ end
934
+ end
935
+
936
+ # Perform a bitwise operation between strings and store the resulting string in a key.
937
+ #
938
+ # @param [String] operation e.g. `and`, `or`, `xor`, `not`
939
+ # @param [String] destkey destination key
940
+ # @param [String, Array<String>] keys one or more source keys to perform `operation`
941
+ # @return [Fixnum] the length of the string stored in `destkey`
942
+ def bitop(operation, destkey, *keys)
943
+ synchronize do |client|
944
+ client.call([:bitop, operation, destkey] + keys)
945
+ end
946
+ end
947
+
948
+ # Return the position of the first bit set to 1 or 0 in a string.
949
+ #
950
+ # @param [String] key
951
+ # @param [Fixnum] bit whether to look for the first 1 or 0 bit
952
+ # @param [Fixnum] start start index
953
+ # @param [Fixnum] stop stop index
954
+ # @return [Fixnum] the position of the first 1/0 bit.
955
+ # -1 if looking for 1 and it is not found or start and stop are given.
956
+ def bitpos(key, bit, start=nil, stop=nil)
957
+ if stop and not start
958
+ raise(ArgumentError, 'stop parameter specified without start parameter')
959
+ end
960
+
961
+ synchronize do |client|
962
+ command = [:bitpos, key, bit]
963
+ command << start if start
964
+ command << stop if stop
965
+ client.call(command)
966
+ end
967
+ end
968
+
969
+ # Set the string value of a key and return its old value.
970
+ #
971
+ # @param [String] key
972
+ # @param [String] value value to replace the current value with
973
+ # @return [String] the old value stored in the key, or `nil` if the key
974
+ # did not exist
975
+ def getset(key, value)
976
+ synchronize do |client|
977
+ client.call([:getset, key, value.to_s])
978
+ end
979
+ end
980
+
981
+ # Get the length of the value stored in a key.
982
+ #
983
+ # @param [String] key
984
+ # @return [Fixnum] the length of the value stored in the key, or 0
985
+ # if the key does not exist
986
+ def strlen(key)
987
+ synchronize do |client|
988
+ client.call([:strlen, key])
989
+ end
990
+ end
991
+
992
+ # Get the length of a list.
993
+ #
994
+ # @param [String] key
995
+ # @return [Fixnum]
996
+ def llen(key)
997
+ synchronize do |client|
998
+ client.call([:llen, key])
999
+ end
1000
+ end
1001
+
1002
+ # Prepend one or more values to a list, creating the list if it doesn't exist
1003
+ #
1004
+ # @param [String] key
1005
+ # @param [String, Array] value string value, or array of string values to push
1006
+ # @return [Fixnum] the length of the list after the push operation
1007
+ def lpush(key, value)
1008
+ synchronize do |client|
1009
+ client.call([:lpush, key, value])
1010
+ end
1011
+ end
1012
+
1013
+ # Prepend a value to a list, only if the list exists.
1014
+ #
1015
+ # @param [String] key
1016
+ # @param [String] value
1017
+ # @return [Fixnum] the length of the list after the push operation
1018
+ def lpushx(key, value)
1019
+ synchronize do |client|
1020
+ client.call([:lpushx, key, value])
1021
+ end
1022
+ end
1023
+
1024
+ # Append one or more values to a list, creating the list if it doesn't exist
1025
+ #
1026
+ # @param [String] key
1027
+ # @param [String] value
1028
+ # @return [Fixnum] the length of the list after the push operation
1029
+ def rpush(key, value)
1030
+ synchronize do |client|
1031
+ client.call([:rpush, key, value])
1032
+ end
1033
+ end
1034
+
1035
+ # Append a value to a list, only if the list exists.
1036
+ #
1037
+ # @param [String] key
1038
+ # @param [String] value
1039
+ # @return [Fixnum] the length of the list after the push operation
1040
+ def rpushx(key, value)
1041
+ synchronize do |client|
1042
+ client.call([:rpushx, key, value])
1043
+ end
1044
+ end
1045
+
1046
+ # Remove and get the first element in a list.
1047
+ #
1048
+ # @param [String] key
1049
+ # @return [String]
1050
+ def lpop(key)
1051
+ synchronize do |client|
1052
+ client.call([:lpop, key])
1053
+ end
1054
+ end
1055
+
1056
+ # Remove and get the last element in a list.
1057
+ #
1058
+ # @param [String] key
1059
+ # @return [String]
1060
+ def rpop(key)
1061
+ synchronize do |client|
1062
+ client.call([:rpop, key])
1063
+ end
1064
+ end
1065
+
1066
+ # Remove the last element in a list, append it to another list and return it.
1067
+ #
1068
+ # @param [String] source source key
1069
+ # @param [String] destination destination key
1070
+ # @return [nil, String] the element, or nil when the source key does not exist
1071
+ def rpoplpush(source, destination)
1072
+ synchronize do |client|
1073
+ client.call([:rpoplpush, source, destination])
1074
+ end
1075
+ end
1076
+
1077
+ def _bpop(cmd, args)
1078
+ options = {}
1079
+
1080
+ case args.last
1081
+ when Hash
1082
+ options = args.pop
1083
+ when Integer
1084
+ # Issue deprecation notice in obnoxious mode...
1085
+ options[:timeout] = args.pop
1086
+ end
1087
+
1088
+ if args.size > 1
1089
+ # Issue deprecation notice in obnoxious mode...
1090
+ end
1091
+
1092
+ keys = args.flatten
1093
+ timeout = options[:timeout] || 0
1094
+
1095
+ synchronize do |client|
1096
+ command = [cmd, keys, timeout]
1097
+ timeout += client.timeout if timeout > 0
1098
+ client.call_with_timeout(command, timeout)
1099
+ end
1100
+ end
1101
+
1102
+ # Remove and get the first element in a list, or block until one is available.
1103
+ #
1104
+ # @example With timeout
1105
+ # list, element = redis.blpop("list", :timeout => 5)
1106
+ # # => nil on timeout
1107
+ # # => ["list", "element"] on success
1108
+ # @example Without timeout
1109
+ # list, element = redis.blpop("list")
1110
+ # # => ["list", "element"]
1111
+ # @example Blocking pop on multiple lists
1112
+ # list, element = redis.blpop(["list", "another_list"])
1113
+ # # => ["list", "element"]
1114
+ #
1115
+ # @param [String, Array<String>] keys one or more keys to perform the
1116
+ # blocking pop on
1117
+ # @param [Hash] options
1118
+ # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1119
+ #
1120
+ # @return [nil, [String, String]]
1121
+ # - `nil` when the operation timed out
1122
+ # - tuple of the list that was popped from and element was popped otherwise
1123
+ def blpop(*args)
1124
+ _bpop(:blpop, args)
1125
+ end
1126
+
1127
+ # Remove and get the last element in a list, or block until one is available.
1128
+ #
1129
+ # @param [String, Array<String>] keys one or more keys to perform the
1130
+ # blocking pop on
1131
+ # @param [Hash] options
1132
+ # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1133
+ #
1134
+ # @return [nil, [String, String]]
1135
+ # - `nil` when the operation timed out
1136
+ # - tuple of the list that was popped from and element was popped otherwise
1137
+ #
1138
+ # @see #blpop
1139
+ def brpop(*args)
1140
+ _bpop(:brpop, args)
1141
+ end
1142
+
1143
+ # Pop a value from a list, push it to another list and return it; or block
1144
+ # until one is available.
1145
+ #
1146
+ # @param [String] source source key
1147
+ # @param [String] destination destination key
1148
+ # @param [Hash] options
1149
+ # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1150
+ #
1151
+ # @return [nil, String]
1152
+ # - `nil` when the operation timed out
1153
+ # - the element was popped and pushed otherwise
1154
+ def brpoplpush(source, destination, options = {})
1155
+ case options
1156
+ when Integer
1157
+ # Issue deprecation notice in obnoxious mode...
1158
+ options = { :timeout => options }
1159
+ end
1160
+
1161
+ timeout = options[:timeout] || 0
1162
+
1163
+ synchronize do |client|
1164
+ command = [:brpoplpush, source, destination, timeout]
1165
+ timeout += client.timeout if timeout > 0
1166
+ client.call_with_timeout(command, timeout)
1167
+ end
1168
+ end
1169
+
1170
+ # Get an element from a list by its index.
1171
+ #
1172
+ # @param [String] key
1173
+ # @param [Fixnum] index
1174
+ # @return [String]
1175
+ def lindex(key, index)
1176
+ synchronize do |client|
1177
+ client.call([:lindex, key, index])
1178
+ end
1179
+ end
1180
+
1181
+ # Insert an element before or after another element in a list.
1182
+ #
1183
+ # @param [String] key
1184
+ # @param [String, Symbol] where `BEFORE` or `AFTER`
1185
+ # @param [String] pivot reference element
1186
+ # @param [String] value
1187
+ # @return [Fixnum] length of the list after the insert operation, or `-1`
1188
+ # when the element `pivot` was not found
1189
+ def linsert(key, where, pivot, value)
1190
+ synchronize do |client|
1191
+ client.call([:linsert, key, where, pivot, value])
1192
+ end
1193
+ end
1194
+
1195
+ # Get a range of elements from a list.
1196
+ #
1197
+ # @param [String] key
1198
+ # @param [Fixnum] start start index
1199
+ # @param [Fixnum] stop stop index
1200
+ # @return [Array<String>]
1201
+ def lrange(key, start, stop)
1202
+ synchronize do |client|
1203
+ client.call([:lrange, key, start, stop])
1204
+ end
1205
+ end
1206
+
1207
+ # Remove elements from a list.
1208
+ #
1209
+ # @param [String] key
1210
+ # @param [Fixnum] count number of elements to remove. Use a positive
1211
+ # value to remove the first `count` occurrences of `value`. A negative
1212
+ # value to remove the last `count` occurrences of `value`. Or zero, to
1213
+ # remove all occurrences of `value` from the list.
1214
+ # @param [String] value
1215
+ # @return [Fixnum] the number of removed elements
1216
+ def lrem(key, count, value)
1217
+ synchronize do |client|
1218
+ client.call([:lrem, key, count, value])
1219
+ end
1220
+ end
1221
+
1222
+ # Set the value of an element in a list by its index.
1223
+ #
1224
+ # @param [String] key
1225
+ # @param [Fixnum] index
1226
+ # @param [String] value
1227
+ # @return [String] `OK`
1228
+ def lset(key, index, value)
1229
+ synchronize do |client|
1230
+ client.call([:lset, key, index, value])
1231
+ end
1232
+ end
1233
+
1234
+ # Trim a list to the specified range.
1235
+ #
1236
+ # @param [String] key
1237
+ # @param [Fixnum] start start index
1238
+ # @param [Fixnum] stop stop index
1239
+ # @return [String] `OK`
1240
+ def ltrim(key, start, stop)
1241
+ synchronize do |client|
1242
+ client.call([:ltrim, key, start, stop])
1243
+ end
1244
+ end
1245
+
1246
+ # Get the number of members in a set.
1247
+ #
1248
+ # @param [String] key
1249
+ # @return [Fixnum]
1250
+ def scard(key)
1251
+ synchronize do |client|
1252
+ client.call([:scard, key])
1253
+ end
1254
+ end
1255
+
1256
+ # Add one or more members to a set.
1257
+ #
1258
+ # @param [String] key
1259
+ # @param [String, Array<String>] member one member, or array of members
1260
+ # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1261
+ # holding whether or not adding the member succeeded, or `Fixnum` when an
1262
+ # array of members is specified, holding the number of members that were
1263
+ # successfully added
1264
+ def sadd(key, member)
1265
+ synchronize do |client|
1266
+ client.call([:sadd, key, member]) do |reply|
1267
+ if member.is_a? Array
1268
+ # Variadic: return integer
1269
+ reply
1270
+ else
1271
+ # Single argument: return boolean
1272
+ _boolify.call(reply)
1273
+ end
1274
+ end
1275
+ end
1276
+ end
1277
+
1278
+ # Remove one or more members from a set.
1279
+ #
1280
+ # @param [String] key
1281
+ # @param [String, Array<String>] member one member, or array of members
1282
+ # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1283
+ # holding whether or not removing the member succeeded, or `Fixnum` when an
1284
+ # array of members is specified, holding the number of members that were
1285
+ # successfully removed
1286
+ def srem(key, member)
1287
+ synchronize do |client|
1288
+ client.call([:srem, key, member]) do |reply|
1289
+ if member.is_a? Array
1290
+ # Variadic: return integer
1291
+ reply
1292
+ else
1293
+ # Single argument: return boolean
1294
+ _boolify.call(reply)
1295
+ end
1296
+ end
1297
+ end
1298
+ end
1299
+
1300
+ # Remove and return a random member from a set.
1301
+ #
1302
+ # @param [String] key
1303
+ # @return [String]
1304
+ def spop(key)
1305
+ synchronize do |client|
1306
+ client.call([:spop, key])
1307
+ end
1308
+ end
1309
+
1310
+ # Get one or more random members from a set.
1311
+ #
1312
+ # @param [String] key
1313
+ # @param [Fixnum] count
1314
+ # @return [String]
1315
+ def srandmember(key, count = nil)
1316
+ synchronize do |client|
1317
+ if count.nil?
1318
+ client.call([:srandmember, key])
1319
+ else
1320
+ client.call([:srandmember, key, count])
1321
+ end
1322
+ end
1323
+ end
1324
+
1325
+ # Move a member from one set to another.
1326
+ #
1327
+ # @param [String] source source key
1328
+ # @param [String] destination destination key
1329
+ # @param [String] member member to move from `source` to `destination`
1330
+ # @return [Boolean]
1331
+ def smove(source, destination, member)
1332
+ synchronize do |client|
1333
+ client.call([:smove, source, destination, member], &_boolify)
1334
+ end
1335
+ end
1336
+
1337
+ # Determine if a given value is a member of a set.
1338
+ #
1339
+ # @param [String] key
1340
+ # @param [String] member
1341
+ # @return [Boolean]
1342
+ def sismember(key, member)
1343
+ synchronize do |client|
1344
+ client.call([:sismember, key, member], &_boolify)
1345
+ end
1346
+ end
1347
+
1348
+ # Get all the members in a set.
1349
+ #
1350
+ # @param [String] key
1351
+ # @return [Array<String>]
1352
+ def smembers(key)
1353
+ synchronize do |client|
1354
+ client.call([:smembers, key])
1355
+ end
1356
+ end
1357
+
1358
+ # Subtract multiple sets.
1359
+ #
1360
+ # @param [String, Array<String>] keys keys pointing to sets to subtract
1361
+ # @return [Array<String>] members in the difference
1362
+ def sdiff(*keys)
1363
+ synchronize do |client|
1364
+ client.call([:sdiff] + keys)
1365
+ end
1366
+ end
1367
+
1368
+ # Subtract multiple sets and store the resulting set in a key.
1369
+ #
1370
+ # @param [String] destination destination key
1371
+ # @param [String, Array<String>] keys keys pointing to sets to subtract
1372
+ # @return [Fixnum] number of elements in the resulting set
1373
+ def sdiffstore(destination, *keys)
1374
+ synchronize do |client|
1375
+ client.call([:sdiffstore, destination] + keys)
1376
+ end
1377
+ end
1378
+
1379
+ # Intersect multiple sets.
1380
+ #
1381
+ # @param [String, Array<String>] keys keys pointing to sets to intersect
1382
+ # @return [Array<String>] members in the intersection
1383
+ def sinter(*keys)
1384
+ synchronize do |client|
1385
+ client.call([:sinter] + keys)
1386
+ end
1387
+ end
1388
+
1389
+ # Intersect multiple sets and store the resulting set in a key.
1390
+ #
1391
+ # @param [String] destination destination key
1392
+ # @param [String, Array<String>] keys keys pointing to sets to intersect
1393
+ # @return [Fixnum] number of elements in the resulting set
1394
+ def sinterstore(destination, *keys)
1395
+ synchronize do |client|
1396
+ client.call([:sinterstore, destination] + keys)
1397
+ end
1398
+ end
1399
+
1400
+ # Add multiple sets.
1401
+ #
1402
+ # @param [String, Array<String>] keys keys pointing to sets to unify
1403
+ # @return [Array<String>] members in the union
1404
+ def sunion(*keys)
1405
+ synchronize do |client|
1406
+ client.call([:sunion] + keys)
1407
+ end
1408
+ end
1409
+
1410
+ # Add multiple sets and store the resulting set in a key.
1411
+ #
1412
+ # @param [String] destination destination key
1413
+ # @param [String, Array<String>] keys keys pointing to sets to unify
1414
+ # @return [Fixnum] number of elements in the resulting set
1415
+ def sunionstore(destination, *keys)
1416
+ synchronize do |client|
1417
+ client.call([:sunionstore, destination] + keys)
1418
+ end
1419
+ end
1420
+
1421
+ # Get the number of members in a sorted set.
1422
+ #
1423
+ # @example
1424
+ # redis.zcard("zset")
1425
+ # # => 4
1426
+ #
1427
+ # @param [String] key
1428
+ # @return [Fixnum]
1429
+ def zcard(key)
1430
+ synchronize do |client|
1431
+ client.call([:zcard, key])
1432
+ end
1433
+ end
1434
+
1435
+ # Add one or more members to a sorted set, or update the score for members
1436
+ # that already exist.
1437
+ #
1438
+ # @example Add a single `[score, member]` pair to a sorted set
1439
+ # redis.zadd("zset", 32.0, "member")
1440
+ # @example Add an array of `[score, member]` pairs to a sorted set
1441
+ # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]])
1442
+ #
1443
+ # @param [String] key
1444
+ # @param [[Float, String], Array<[Float, String]>] args
1445
+ # - a single `[score, member]` pair
1446
+ # - an array of `[score, member]` pairs
1447
+ # @param [Hash] options
1448
+ # - `:xx => true`: Only update elements that already exist (never
1449
+ # add elements)
1450
+ # - `:nx => true`: Don't update already existing elements (always
1451
+ # add new elements)
1452
+ # - `:ch => true`: Modify the return value from the number of new
1453
+ # elements added, to the total number of elements changed (CH is an
1454
+ # abbreviation of changed); changed elements are new elements added
1455
+ # and elements already existing for which the score was updated
1456
+ # - `:incr => true`: When this option is specified ZADD acts like
1457
+ # ZINCRBY; only one score-element pair can be specified in this mode
1458
+ #
1459
+ # @return [Boolean, Fixnum, Float]
1460
+ # - `Boolean` when a single pair is specified, holding whether or not it was
1461
+ # **added** to the sorted set.
1462
+ # - `Fixnum` when an array of pairs is specified, holding the number of
1463
+ # pairs that were **added** to the sorted set.
1464
+ # - `Float` when option :incr is specified, holding the score of the member
1465
+ # after incrementing it.
1466
+ def zadd(key, *args) #, options
1467
+ zadd_options = []
1468
+ if args.last.is_a?(Hash)
1469
+ options = args.pop
1470
+
1471
+ nx = options[:nx]
1472
+ zadd_options << "NX" if nx
1473
+
1474
+ xx = options[:xx]
1475
+ zadd_options << "XX" if xx
1476
+
1477
+ ch = options[:ch]
1478
+ zadd_options << "CH" if ch
1479
+
1480
+ incr = options[:incr]
1481
+ zadd_options << "INCR" if incr
1482
+ end
1483
+
1484
+ synchronize do |client|
1485
+ if args.size == 1 && args[0].is_a?(Array)
1486
+ # Variadic: return float if INCR, integer if !INCR
1487
+ client.call([:zadd, key] + zadd_options + args[0], &(incr ? _floatify : _identity))
1488
+ elsif args.size == 2
1489
+ # Single pair: return float if INCR, boolean if !INCR
1490
+ client.call([:zadd, key] + zadd_options + args, &(incr ? _floatify : _boolify))
1491
+ else
1492
+ raise ArgumentError, "wrong number of arguments"
1493
+ end
1494
+ end
1495
+ end
1496
+
1497
+ # Increment the score of a member in a sorted set.
1498
+ #
1499
+ # @example
1500
+ # redis.zincrby("zset", 32.0, "a")
1501
+ # # => 64.0
1502
+ #
1503
+ # @param [String] key
1504
+ # @param [Float] increment
1505
+ # @param [String] member
1506
+ # @return [Float] score of the member after incrementing it
1507
+ def zincrby(key, increment, member)
1508
+ synchronize do |client|
1509
+ client.call([:zincrby, key, increment, member], &_floatify)
1510
+ end
1511
+ end
1512
+
1513
+ # Remove one or more members from a sorted set.
1514
+ #
1515
+ # @example Remove a single member from a sorted set
1516
+ # redis.zrem("zset", "a")
1517
+ # @example Remove an array of members from a sorted set
1518
+ # redis.zrem("zset", ["a", "b"])
1519
+ #
1520
+ # @param [String] key
1521
+ # @param [String, Array<String>] member
1522
+ # - a single member
1523
+ # - an array of members
1524
+ #
1525
+ # @return [Boolean, Fixnum]
1526
+ # - `Boolean` when a single member is specified, holding whether or not it
1527
+ # was removed from the sorted set
1528
+ # - `Fixnum` when an array of pairs is specified, holding the number of
1529
+ # members that were removed to the sorted set
1530
+ def zrem(key, member)
1531
+ synchronize do |client|
1532
+ client.call([:zrem, key, member]) do |reply|
1533
+ if member.is_a? Array
1534
+ # Variadic: return integer
1535
+ reply
1536
+ else
1537
+ # Single argument: return boolean
1538
+ _boolify.call(reply)
1539
+ end
1540
+ end
1541
+ end
1542
+ end
1543
+
1544
+ # Get the score associated with the given member in a sorted set.
1545
+ #
1546
+ # @example Get the score for member "a"
1547
+ # redis.zscore("zset", "a")
1548
+ # # => 32.0
1549
+ #
1550
+ # @param [String] key
1551
+ # @param [String] member
1552
+ # @return [Float] score of the member
1553
+ def zscore(key, member)
1554
+ synchronize do |client|
1555
+ client.call([:zscore, key, member], &_floatify)
1556
+ end
1557
+ end
1558
+
1559
+ # Return a range of members in a sorted set, by index.
1560
+ #
1561
+ # @example Retrieve all members from a sorted set
1562
+ # redis.zrange("zset", 0, -1)
1563
+ # # => ["a", "b"]
1564
+ # @example Retrieve all members and their scores from a sorted set
1565
+ # redis.zrange("zset", 0, -1, :with_scores => true)
1566
+ # # => [["a", 32.0], ["b", 64.0]]
1567
+ #
1568
+ # @param [String] key
1569
+ # @param [Fixnum] start start index
1570
+ # @param [Fixnum] stop stop index
1571
+ # @param [Hash] options
1572
+ # - `:with_scores => true`: include scores in output
1573
+ #
1574
+ # @return [Array<String>, Array<[String, Float]>]
1575
+ # - when `:with_scores` is not specified, an array of members
1576
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
1577
+ def zrange(key, start, stop, options = {})
1578
+ args = []
1579
+
1580
+ with_scores = options[:with_scores] || options[:withscores]
1581
+
1582
+ if with_scores
1583
+ args << "WITHSCORES"
1584
+ block = _floatify_pairs
1585
+ end
1586
+
1587
+ synchronize do |client|
1588
+ client.call([:zrange, key, start, stop] + args, &block)
1589
+ end
1590
+ end
1591
+
1592
+ # Return a range of members in a sorted set, by index, with scores ordered
1593
+ # from high to low.
1594
+ #
1595
+ # @example Retrieve all members from a sorted set
1596
+ # redis.zrevrange("zset", 0, -1)
1597
+ # # => ["b", "a"]
1598
+ # @example Retrieve all members and their scores from a sorted set
1599
+ # redis.zrevrange("zset", 0, -1, :with_scores => true)
1600
+ # # => [["b", 64.0], ["a", 32.0]]
1601
+ #
1602
+ # @see #zrange
1603
+ def zrevrange(key, start, stop, options = {})
1604
+ args = []
1605
+
1606
+ with_scores = options[:with_scores] || options[:withscores]
1607
+
1608
+ if with_scores
1609
+ args << "WITHSCORES"
1610
+ block = _floatify_pairs
1611
+ end
1612
+
1613
+ synchronize do |client|
1614
+ client.call([:zrevrange, key, start, stop] + args, &block)
1615
+ end
1616
+ end
1617
+
1618
+ # Determine the index of a member in a sorted set.
1619
+ #
1620
+ # @param [String] key
1621
+ # @param [String] member
1622
+ # @return [Fixnum]
1623
+ def zrank(key, member)
1624
+ synchronize do |client|
1625
+ client.call([:zrank, key, member])
1626
+ end
1627
+ end
1628
+
1629
+ # Determine the index of a member in a sorted set, with scores ordered from
1630
+ # high to low.
1631
+ #
1632
+ # @param [String] key
1633
+ # @param [String] member
1634
+ # @return [Fixnum]
1635
+ def zrevrank(key, member)
1636
+ synchronize do |client|
1637
+ client.call([:zrevrank, key, member])
1638
+ end
1639
+ end
1640
+
1641
+ # Remove all members in a sorted set within the given indexes.
1642
+ #
1643
+ # @example Remove first 5 members
1644
+ # redis.zremrangebyrank("zset", 0, 4)
1645
+ # # => 5
1646
+ # @example Remove last 5 members
1647
+ # redis.zremrangebyrank("zset", -5, -1)
1648
+ # # => 5
1649
+ #
1650
+ # @param [String] key
1651
+ # @param [Fixnum] start start index
1652
+ # @param [Fixnum] stop stop index
1653
+ # @return [Fixnum] number of members that were removed
1654
+ def zremrangebyrank(key, start, stop)
1655
+ synchronize do |client|
1656
+ client.call([:zremrangebyrank, key, start, stop])
1657
+ end
1658
+ end
1659
+
1660
+ # Return a range of members with the same score in a sorted set, by lexicographical ordering
1661
+ #
1662
+ # @example Retrieve members matching a
1663
+ # redis.zrangebylex("zset", "[a", "[a\xff")
1664
+ # # => ["aaren", "aarika", "abagael", "abby"]
1665
+ # @example Retrieve the first 2 members matching a
1666
+ # redis.zrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
1667
+ # # => ["aaren", "aarika"]
1668
+ #
1669
+ # @param [String] key
1670
+ # @param [String] min
1671
+ # - inclusive minimum is specified by prefixing `(`
1672
+ # - exclusive minimum is specified by prefixing `[`
1673
+ # @param [String] max
1674
+ # - inclusive maximum is specified by prefixing `(`
1675
+ # - exclusive maximum is specified by prefixing `[`
1676
+ # @param [Hash] options
1677
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
1678
+ # `count` members
1679
+ #
1680
+ # @return [Array<String>, Array<[String, Float]>]
1681
+ def zrangebylex(key, min, max, options = {})
1682
+ args = []
1683
+
1684
+ limit = options[:limit]
1685
+ args.concat(["LIMIT"] + limit) if limit
1686
+
1687
+ synchronize do |client|
1688
+ client.call([:zrangebylex, key, min, max] + args)
1689
+ end
1690
+ end
1691
+
1692
+ # Return a range of members with the same score in a sorted set, by reversed lexicographical ordering.
1693
+ # Apart from the reversed ordering, #zrevrangebylex is similar to #zrangebylex.
1694
+ #
1695
+ # @example Retrieve members matching a
1696
+ # redis.zrevrangebylex("zset", "[a", "[a\xff")
1697
+ # # => ["abbygail", "abby", "abagael", "aaren"]
1698
+ # @example Retrieve the last 2 members matching a
1699
+ # redis.zrevrangebylex("zset", "[a", "[a\xff", :limit => [0, 2])
1700
+ # # => ["abbygail", "abby"]
1701
+ #
1702
+ # @see #zrangebylex
1703
+ def zrevrangebylex(key, max, min, options = {})
1704
+ args = []
1705
+
1706
+ limit = options[:limit]
1707
+ args.concat(["LIMIT"] + limit) if limit
1708
+
1709
+ synchronize do |client|
1710
+ client.call([:zrevrangebylex, key, max, min] + args)
1711
+ end
1712
+ end
1713
+
1714
+ # Return a range of members in a sorted set, by score.
1715
+ #
1716
+ # @example Retrieve members with score `>= 5` and `< 100`
1717
+ # redis.zrangebyscore("zset", "5", "(100")
1718
+ # # => ["a", "b"]
1719
+ # @example Retrieve the first 2 members with score `>= 0`
1720
+ # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
1721
+ # # => ["a", "b"]
1722
+ # @example Retrieve members and their scores with scores `> 5`
1723
+ # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
1724
+ # # => [["a", 32.0], ["b", 64.0]]
1725
+ #
1726
+ # @param [String] key
1727
+ # @param [String] min
1728
+ # - inclusive minimum score is specified verbatim
1729
+ # - exclusive minimum score is specified by prefixing `(`
1730
+ # @param [String] max
1731
+ # - inclusive maximum score is specified verbatim
1732
+ # - exclusive maximum score is specified by prefixing `(`
1733
+ # @param [Hash] options
1734
+ # - `:with_scores => true`: include scores in output
1735
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
1736
+ # `count` members
1737
+ #
1738
+ # @return [Array<String>, Array<[String, Float]>]
1739
+ # - when `:with_scores` is not specified, an array of members
1740
+ # - when `:with_scores` is specified, an array with `[member, score]` pairs
1741
+ def zrangebyscore(key, min, max, options = {})
1742
+ args = []
1743
+
1744
+ with_scores = options[:with_scores] || options[:withscores]
1745
+
1746
+ if with_scores
1747
+ args << "WITHSCORES"
1748
+ block = _floatify_pairs
1749
+ end
1750
+
1751
+ limit = options[:limit]
1752
+ args.concat(["LIMIT"] + limit) if limit
1753
+
1754
+ synchronize do |client|
1755
+ client.call([:zrangebyscore, key, min, max] + args, &block)
1756
+ end
1757
+ end
1758
+
1759
+ # Return a range of members in a sorted set, by score, with scores ordered
1760
+ # from high to low.
1761
+ #
1762
+ # @example Retrieve members with score `< 100` and `>= 5`
1763
+ # redis.zrevrangebyscore("zset", "(100", "5")
1764
+ # # => ["b", "a"]
1765
+ # @example Retrieve the first 2 members with score `<= 0`
1766
+ # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
1767
+ # # => ["b", "a"]
1768
+ # @example Retrieve members and their scores with scores `> 5`
1769
+ # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
1770
+ # # => [["b", 64.0], ["a", 32.0]]
1771
+ #
1772
+ # @see #zrangebyscore
1773
+ def zrevrangebyscore(key, max, min, options = {})
1774
+ args = []
1775
+
1776
+ with_scores = options[:with_scores] || options[:withscores]
1777
+
1778
+ if with_scores
1779
+ args << ["WITHSCORES"]
1780
+ block = _floatify_pairs
1781
+ end
1782
+
1783
+ limit = options[:limit]
1784
+ args.concat(["LIMIT"] + limit) if limit
1785
+
1786
+ synchronize do |client|
1787
+ client.call([:zrevrangebyscore, key, max, min] + args, &block)
1788
+ end
1789
+ end
1790
+
1791
+ # Remove all members in a sorted set within the given scores.
1792
+ #
1793
+ # @example Remove members with score `>= 5` and `< 100`
1794
+ # redis.zremrangebyscore("zset", "5", "(100")
1795
+ # # => 2
1796
+ # @example Remove members with scores `> 5`
1797
+ # redis.zremrangebyscore("zset", "(5", "+inf")
1798
+ # # => 2
1799
+ #
1800
+ # @param [String] key
1801
+ # @param [String] min
1802
+ # - inclusive minimum score is specified verbatim
1803
+ # - exclusive minimum score is specified by prefixing `(`
1804
+ # @param [String] max
1805
+ # - inclusive maximum score is specified verbatim
1806
+ # - exclusive maximum score is specified by prefixing `(`
1807
+ # @return [Fixnum] number of members that were removed
1808
+ def zremrangebyscore(key, min, max)
1809
+ synchronize do |client|
1810
+ client.call([:zremrangebyscore, key, min, max])
1811
+ end
1812
+ end
1813
+
1814
+ # Count the members in a sorted set with scores within the given values.
1815
+ #
1816
+ # @example Count members with score `>= 5` and `< 100`
1817
+ # redis.zcount("zset", "5", "(100")
1818
+ # # => 2
1819
+ # @example Count members with scores `> 5`
1820
+ # redis.zcount("zset", "(5", "+inf")
1821
+ # # => 2
1822
+ #
1823
+ # @param [String] key
1824
+ # @param [String] min
1825
+ # - inclusive minimum score is specified verbatim
1826
+ # - exclusive minimum score is specified by prefixing `(`
1827
+ # @param [String] max
1828
+ # - inclusive maximum score is specified verbatim
1829
+ # - exclusive maximum score is specified by prefixing `(`
1830
+ # @return [Fixnum] number of members in within the specified range
1831
+ def zcount(key, min, max)
1832
+ synchronize do |client|
1833
+ client.call([:zcount, key, min, max])
1834
+ end
1835
+ end
1836
+
1837
+ # Intersect multiple sorted sets and store the resulting sorted set in a new
1838
+ # key.
1839
+ #
1840
+ # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
1841
+ # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
1842
+ # # => 4
1843
+ #
1844
+ # @param [String] destination destination key
1845
+ # @param [Array<String>] keys source keys
1846
+ # @param [Hash] options
1847
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
1848
+ # sorted sets
1849
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1850
+ # @return [Fixnum] number of elements in the resulting sorted set
1851
+ def zinterstore(destination, keys, options = {})
1852
+ args = []
1853
+
1854
+ weights = options[:weights]
1855
+ args.concat(["WEIGHTS"] + weights) if weights
1856
+
1857
+ aggregate = options[:aggregate]
1858
+ args.concat(["AGGREGATE", aggregate]) if aggregate
1859
+
1860
+ synchronize do |client|
1861
+ client.call([:zinterstore, destination, keys.size] + keys + args)
1862
+ end
1863
+ end
1864
+
1865
+ # Add multiple sorted sets and store the resulting sorted set in a new key.
1866
+ #
1867
+ # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
1868
+ # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
1869
+ # # => 8
1870
+ #
1871
+ # @param [String] destination destination key
1872
+ # @param [Array<String>] keys source keys
1873
+ # @param [Hash] options
1874
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
1875
+ # sorted sets
1876
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1877
+ # @return [Fixnum] number of elements in the resulting sorted set
1878
+ def zunionstore(destination, keys, options = {})
1879
+ args = []
1880
+
1881
+ weights = options[:weights]
1882
+ args.concat(["WEIGHTS"] + weights) if weights
1883
+
1884
+ aggregate = options[:aggregate]
1885
+ args.concat(["AGGREGATE", aggregate]) if aggregate
1886
+
1887
+ synchronize do |client|
1888
+ client.call([:zunionstore, destination, keys.size] + keys + args)
1889
+ end
1890
+ end
1891
+
1892
+ # Get the number of fields in a hash.
1893
+ #
1894
+ # @param [String] key
1895
+ # @return [Fixnum] number of fields in the hash
1896
+ def hlen(key)
1897
+ synchronize do |client|
1898
+ client.call([:hlen, key])
1899
+ end
1900
+ end
1901
+
1902
+ # Set the string value of a hash field.
1903
+ #
1904
+ # @param [String] key
1905
+ # @param [String] field
1906
+ # @param [String] value
1907
+ # @return [Boolean] whether or not the field was **added** to the hash
1908
+ def hset(key, field, value)
1909
+ synchronize do |client|
1910
+ client.call([:hset, key, field, value], &_boolify)
1911
+ end
1912
+ end
1913
+
1914
+ # Set the value of a hash field, only if the field does not exist.
1915
+ #
1916
+ # @param [String] key
1917
+ # @param [String] field
1918
+ # @param [String] value
1919
+ # @return [Boolean] whether or not the field was **added** to the hash
1920
+ def hsetnx(key, field, value)
1921
+ synchronize do |client|
1922
+ client.call([:hsetnx, key, field, value], &_boolify)
1923
+ end
1924
+ end
1925
+
1926
+ # Set one or more hash values.
1927
+ #
1928
+ # @example
1929
+ # redis.hmset("hash", "f1", "v1", "f2", "v2")
1930
+ # # => "OK"
1931
+ #
1932
+ # @param [String] key
1933
+ # @param [Array<String>] attrs array of fields and values
1934
+ # @return [String] `"OK"`
1935
+ #
1936
+ # @see #mapped_hmset
1937
+ def hmset(key, *attrs)
1938
+ synchronize do |client|
1939
+ client.call([:hmset, key] + attrs)
1940
+ end
1941
+ end
1942
+
1943
+ # Set one or more hash values.
1944
+ #
1945
+ # @example
1946
+ # redis.mapped_hmset("hash", { "f1" => "v1", "f2" => "v2" })
1947
+ # # => "OK"
1948
+ #
1949
+ # @param [String] key
1950
+ # @param [Hash] hash a non-empty hash with fields mapping to values
1951
+ # @return [String] `"OK"`
1952
+ #
1953
+ # @see #hmset
1954
+ def mapped_hmset(key, hash)
1955
+ hmset(key, hash.to_a.flatten)
1956
+ end
1957
+
1958
+ # Get the value of a hash field.
1959
+ #
1960
+ # @param [String] key
1961
+ # @param [String] field
1962
+ # @return [String]
1963
+ def hget(key, field)
1964
+ synchronize do |client|
1965
+ client.call([:hget, key, field])
1966
+ end
1967
+ end
1968
+
1969
+ # Get the values of all the given hash fields.
1970
+ #
1971
+ # @example
1972
+ # redis.hmget("hash", "f1", "f2")
1973
+ # # => ["v1", "v2"]
1974
+ #
1975
+ # @param [String] key
1976
+ # @param [Array<String>] fields array of fields
1977
+ # @return [Array<String>] an array of values for the specified fields
1978
+ #
1979
+ # @see #mapped_hmget
1980
+ def hmget(key, *fields, &blk)
1981
+ synchronize do |client|
1982
+ client.call([:hmget, key] + fields, &blk)
1983
+ end
1984
+ end
1985
+
1986
+ # Get the values of all the given hash fields.
1987
+ #
1988
+ # @example
1989
+ # redis.mapped_hmget("hash", "f1", "f2")
1990
+ # # => { "f1" => "v1", "f2" => "v2" }
1991
+ #
1992
+ # @param [String] key
1993
+ # @param [Array<String>] fields array of fields
1994
+ # @return [Hash] a hash mapping the specified fields to their values
1995
+ #
1996
+ # @see #hmget
1997
+ def mapped_hmget(key, *fields)
1998
+ hmget(key, *fields) do |reply|
1999
+ if reply.kind_of?(Array)
2000
+ Hash[fields.zip(reply)]
2001
+ else
2002
+ reply
2003
+ end
2004
+ end
2005
+ end
2006
+
2007
+ # Delete one or more hash fields.
2008
+ #
2009
+ # @param [String] key
2010
+ # @param [String, Array<String>] field
2011
+ # @return [Fixnum] the number of fields that were removed from the hash
2012
+ def hdel(key, field)
2013
+ synchronize do |client|
2014
+ client.call([:hdel, key, field])
2015
+ end
2016
+ end
2017
+
2018
+ # Determine if a hash field exists.
2019
+ #
2020
+ # @param [String] key
2021
+ # @param [String] field
2022
+ # @return [Boolean] whether or not the field exists in the hash
2023
+ def hexists(key, field)
2024
+ synchronize do |client|
2025
+ client.call([:hexists, key, field], &_boolify)
2026
+ end
2027
+ end
2028
+
2029
+ # Increment the integer value of a hash field by the given integer number.
2030
+ #
2031
+ # @param [String] key
2032
+ # @param [String] field
2033
+ # @param [Fixnum] increment
2034
+ # @return [Fixnum] value of the field after incrementing it
2035
+ def hincrby(key, field, increment)
2036
+ synchronize do |client|
2037
+ client.call([:hincrby, key, field, increment])
2038
+ end
2039
+ end
2040
+
2041
+ # Increment the numeric value of a hash field by the given float number.
2042
+ #
2043
+ # @param [String] key
2044
+ # @param [String] field
2045
+ # @param [Float] increment
2046
+ # @return [Float] value of the field after incrementing it
2047
+ def hincrbyfloat(key, field, increment)
2048
+ synchronize do |client|
2049
+ client.call([:hincrbyfloat, key, field, increment], &_floatify)
2050
+ end
2051
+ end
2052
+
2053
+ # Get all the fields in a hash.
2054
+ #
2055
+ # @param [String] key
2056
+ # @return [Array<String>]
2057
+ def hkeys(key)
2058
+ synchronize do |client|
2059
+ client.call([:hkeys, key])
2060
+ end
2061
+ end
2062
+
2063
+ # Get all the values in a hash.
2064
+ #
2065
+ # @param [String] key
2066
+ # @return [Array<String>]
2067
+ def hvals(key)
2068
+ synchronize do |client|
2069
+ client.call([:hvals, key])
2070
+ end
2071
+ end
2072
+
2073
+ # Get all the fields and values in a hash.
2074
+ #
2075
+ # @param [String] key
2076
+ # @return [Hash<String, String>]
2077
+ def hgetall(key)
2078
+ synchronize do |client|
2079
+ client.call([:hgetall, key], &_hashify)
2080
+ end
2081
+ end
2082
+
2083
+ # Post a message to a channel.
2084
+ def publish(channel, message)
2085
+ synchronize do |client|
2086
+ client.call([:publish, channel, message])
2087
+ end
2088
+ end
2089
+
2090
+ def subscribed?
2091
+ synchronize do |client|
2092
+ client.kind_of? SubscribedClient
2093
+ end
2094
+ end
2095
+
2096
+ # Listen for messages published to the given channels.
2097
+ def subscribe(*channels, &block)
2098
+ synchronize do |client|
2099
+ _subscription(:subscribe, channels, block)
2100
+ end
2101
+ end
2102
+
2103
+ # Stop listening for messages posted to the given channels.
2104
+ def unsubscribe(*channels)
2105
+ synchronize do |client|
2106
+ raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2107
+ client.unsubscribe(*channels)
2108
+ end
2109
+ end
2110
+
2111
+ # Listen for messages published to channels matching the given patterns.
2112
+ def psubscribe(*channels, &block)
2113
+ synchronize do |client|
2114
+ _subscription(:psubscribe, channels, block)
2115
+ end
2116
+ end
2117
+
2118
+ # Stop listening for messages posted to channels matching the given patterns.
2119
+ def punsubscribe(*channels)
2120
+ synchronize do |client|
2121
+ raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2122
+ client.punsubscribe(*channels)
2123
+ end
2124
+ end
2125
+
2126
+ # Inspect the state of the Pub/Sub subsystem.
2127
+ # Possible subcommands: channels, numsub, numpat.
2128
+ def pubsub(subcommand, *args)
2129
+ synchronize do |client|
2130
+ client.call([:pubsub, subcommand] + args)
2131
+ end
2132
+ end
2133
+
2134
+ # Watch the given keys to determine execution of the MULTI/EXEC block.
2135
+ #
2136
+ # Using a block is optional, but is necessary for thread-safety.
2137
+ #
2138
+ # An `#unwatch` is automatically issued if an exception is raised within the
2139
+ # block that is a subclass of StandardError and is not a ConnectionError.
2140
+ #
2141
+ # @example With a block
2142
+ # redis.watch("key") do
2143
+ # if redis.get("key") == "some value"
2144
+ # redis.multi do |multi|
2145
+ # multi.set("key", "other value")
2146
+ # multi.incr("counter")
2147
+ # end
2148
+ # else
2149
+ # redis.unwatch
2150
+ # end
2151
+ # end
2152
+ # # => ["OK", 6]
2153
+ #
2154
+ # @example Without a block
2155
+ # redis.watch("key")
2156
+ # # => "OK"
2157
+ #
2158
+ # @param [String, Array<String>] keys one or more keys to watch
2159
+ # @return [Object] if using a block, returns the return value of the block
2160
+ # @return [String] if not using a block, returns `OK`
2161
+ #
2162
+ # @see #unwatch
2163
+ # @see #multi
2164
+ def watch(*keys)
2165
+ synchronize do |client|
2166
+ res = client.call([:watch] + keys)
2167
+
2168
+ if block_given?
2169
+ begin
2170
+ yield(self)
2171
+ rescue ConnectionError
2172
+ raise
2173
+ rescue StandardError
2174
+ unwatch
2175
+ raise
2176
+ end
2177
+ else
2178
+ res
2179
+ end
2180
+ end
2181
+ end
2182
+
2183
+ # Forget about all watched keys.
2184
+ #
2185
+ # @return [String] `OK`
2186
+ #
2187
+ # @see #watch
2188
+ # @see #multi
2189
+ def unwatch
2190
+ synchronize do |client|
2191
+ client.call([:unwatch])
2192
+ end
2193
+ end
2194
+
2195
+ def pipelined
2196
+ synchronize do |client|
2197
+ begin
2198
+ original, @client = @client, Pipeline.new
2199
+ yield(self)
2200
+ original.call_pipeline(@client)
2201
+ ensure
2202
+ @client = original
2203
+ end
2204
+ end
2205
+ end
2206
+
2207
+ # Mark the start of a transaction block.
2208
+ #
2209
+ # Passing a block is optional.
2210
+ #
2211
+ # @example With a block
2212
+ # redis.multi do |multi|
2213
+ # multi.set("key", "value")
2214
+ # multi.incr("counter")
2215
+ # end # => ["OK", 6]
2216
+ #
2217
+ # @example Without a block
2218
+ # redis.multi
2219
+ # # => "OK"
2220
+ # redis.set("key", "value")
2221
+ # # => "QUEUED"
2222
+ # redis.incr("counter")
2223
+ # # => "QUEUED"
2224
+ # redis.exec
2225
+ # # => ["OK", 6]
2226
+ #
2227
+ # @yield [multi] the commands that are called inside this block are cached
2228
+ # and written to the server upon returning from it
2229
+ # @yieldparam [Redis] multi `self`
2230
+ #
2231
+ # @return [String, Array<...>]
2232
+ # - when a block is not given, `OK`
2233
+ # - when a block is given, an array with replies
2234
+ #
2235
+ # @see #watch
2236
+ # @see #unwatch
2237
+ def multi
2238
+ synchronize do |client|
2239
+ if !block_given?
2240
+ client.call([:multi])
2241
+ else
2242
+ begin
2243
+ pipeline = Pipeline::Multi.new
2244
+ original, @client = @client, pipeline
2245
+ yield(self)
2246
+ original.call_pipeline(pipeline)
2247
+ ensure
2248
+ @client = original
2249
+ end
2250
+ end
2251
+ end
2252
+ end
2253
+
2254
+ # Execute all commands issued after MULTI.
2255
+ #
2256
+ # Only call this method when `#multi` was called **without** a block.
2257
+ #
2258
+ # @return [nil, Array<...>]
2259
+ # - when commands were not executed, `nil`
2260
+ # - when commands were executed, an array with their replies
2261
+ #
2262
+ # @see #multi
2263
+ # @see #discard
2264
+ def exec
2265
+ synchronize do |client|
2266
+ client.call([:exec])
2267
+ end
2268
+ end
2269
+
2270
+ # Discard all commands issued after MULTI.
2271
+ #
2272
+ # Only call this method when `#multi` was called **without** a block.
2273
+ #
2274
+ # @return [String] `"OK"`
2275
+ #
2276
+ # @see #multi
2277
+ # @see #exec
2278
+ def discard
2279
+ synchronize do |client|
2280
+ client.call([:discard])
2281
+ end
2282
+ end
2283
+
2284
+ # Control remote script registry.
2285
+ #
2286
+ # @example Load a script
2287
+ # sha = redis.script(:load, "return 1")
2288
+ # # => <sha of this script>
2289
+ # @example Check if a script exists
2290
+ # redis.script(:exists, sha)
2291
+ # # => true
2292
+ # @example Check if multiple scripts exist
2293
+ # redis.script(:exists, [sha, other_sha])
2294
+ # # => [true, false]
2295
+ # @example Flush the script registry
2296
+ # redis.script(:flush)
2297
+ # # => "OK"
2298
+ # @example Kill a running script
2299
+ # redis.script(:kill)
2300
+ # # => "OK"
2301
+ #
2302
+ # @param [String] subcommand e.g. `exists`, `flush`, `load`, `kill`
2303
+ # @param [Array<String>] args depends on subcommand
2304
+ # @return [String, Boolean, Array<Boolean>, ...] depends on subcommand
2305
+ #
2306
+ # @see #eval
2307
+ # @see #evalsha
2308
+ def script(subcommand, *args)
2309
+ subcommand = subcommand.to_s.downcase
2310
+
2311
+ if subcommand == "exists"
2312
+ synchronize do |client|
2313
+ arg = args.first
2314
+
2315
+ client.call([:script, :exists, arg]) do |reply|
2316
+ reply = reply.map { |r| _boolify.call(r) }
2317
+
2318
+ if arg.is_a?(Array)
2319
+ reply
2320
+ else
2321
+ reply.first
2322
+ end
2323
+ end
2324
+ end
2325
+ else
2326
+ synchronize do |client|
2327
+ client.call([:script, subcommand] + args)
2328
+ end
2329
+ end
2330
+ end
2331
+
2332
+ def _eval(cmd, args)
2333
+ script = args.shift
2334
+ options = args.pop if args.last.is_a?(Hash)
2335
+ options ||= {}
2336
+
2337
+ keys = args.shift || options[:keys] || []
2338
+ argv = args.shift || options[:argv] || []
2339
+
2340
+ synchronize do |client|
2341
+ client.call([cmd, script, keys.length] + keys + argv)
2342
+ end
2343
+ end
2344
+
2345
+ # Evaluate Lua script.
2346
+ #
2347
+ # @example EVAL without KEYS nor ARGV
2348
+ # redis.eval("return 1")
2349
+ # # => 1
2350
+ # @example EVAL with KEYS and ARGV as array arguments
2351
+ # redis.eval("return { KEYS, ARGV }", ["k1", "k2"], ["a1", "a2"])
2352
+ # # => [["k1", "k2"], ["a1", "a2"]]
2353
+ # @example EVAL with KEYS and ARGV in a hash argument
2354
+ # redis.eval("return { KEYS, ARGV }", :keys => ["k1", "k2"], :argv => ["a1", "a2"])
2355
+ # # => [["k1", "k2"], ["a1", "a2"]]
2356
+ #
2357
+ # @param [Array<String>] keys optional array with keys to pass to the script
2358
+ # @param [Array<String>] argv optional array with arguments to pass to the script
2359
+ # @param [Hash] options
2360
+ # - `:keys => Array<String>`: optional array with keys to pass to the script
2361
+ # - `:argv => Array<String>`: optional array with arguments to pass to the script
2362
+ # @return depends on the script
2363
+ #
2364
+ # @see #script
2365
+ # @see #evalsha
2366
+ def eval(*args)
2367
+ _eval(:eval, args)
2368
+ end
2369
+
2370
+ # Evaluate Lua script by its SHA.
2371
+ #
2372
+ # @example EVALSHA without KEYS nor ARGV
2373
+ # redis.evalsha(sha)
2374
+ # # => <depends on script>
2375
+ # @example EVALSHA with KEYS and ARGV as array arguments
2376
+ # redis.evalsha(sha, ["k1", "k2"], ["a1", "a2"])
2377
+ # # => <depends on script>
2378
+ # @example EVALSHA with KEYS and ARGV in a hash argument
2379
+ # redis.evalsha(sha, :keys => ["k1", "k2"], :argv => ["a1", "a2"])
2380
+ # # => <depends on script>
2381
+ #
2382
+ # @param [Array<String>] keys optional array with keys to pass to the script
2383
+ # @param [Array<String>] argv optional array with arguments to pass to the script
2384
+ # @param [Hash] options
2385
+ # - `:keys => Array<String>`: optional array with keys to pass to the script
2386
+ # - `:argv => Array<String>`: optional array with arguments to pass to the script
2387
+ # @return depends on the script
2388
+ #
2389
+ # @see #script
2390
+ # @see #eval
2391
+ def evalsha(*args)
2392
+ _eval(:evalsha, args)
2393
+ end
2394
+
2395
+ def _scan(command, cursor, args, options = {}, &block)
2396
+ # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
2397
+
2398
+ args << cursor
2399
+
2400
+ if match = options[:match]
2401
+ args.concat(["MATCH", match])
2402
+ end
2403
+
2404
+ if count = options[:count]
2405
+ args.concat(["COUNT", count])
2406
+ end
2407
+
2408
+ synchronize do |client|
2409
+ client.call([command] + args, &block)
2410
+ end
2411
+ end
2412
+
2413
+ # Scan the keyspace
2414
+ #
2415
+ # @example Retrieve the first batch of keys
2416
+ # redis.scan(0)
2417
+ # # => ["4", ["key:21", "key:47", "key:42"]]
2418
+ # @example Retrieve a batch of keys matching a pattern
2419
+ # redis.scan(4, :match => "key:1?")
2420
+ # # => ["92", ["key:13", "key:18"]]
2421
+ #
2422
+ # @param [String, Integer] cursor the cursor of the iteration
2423
+ # @param [Hash] options
2424
+ # - `:match => String`: only return keys matching the pattern
2425
+ # - `:count => Integer`: return count keys at most per iteration
2426
+ #
2427
+ # @return [String, Array<String>] the next cursor and all found keys
2428
+ def scan(cursor, options={})
2429
+ _scan(:scan, cursor, [], options)
2430
+ end
2431
+
2432
+ # Scan the keyspace
2433
+ #
2434
+ # @example Retrieve all of the keys (with possible duplicates)
2435
+ # redis.scan_each.to_a
2436
+ # # => ["key:21", "key:47", "key:42"]
2437
+ # @example Execute block for each key matching a pattern
2438
+ # redis.scan_each(:match => "key:1?") {|key| puts key}
2439
+ # # => key:13
2440
+ # # => key:18
2441
+ #
2442
+ # @param [Hash] options
2443
+ # - `:match => String`: only return keys matching the pattern
2444
+ # - `:count => Integer`: return count keys at most per iteration
2445
+ #
2446
+ # @return [Enumerator] an enumerator for all found keys
2447
+ def scan_each(options={}, &block)
2448
+ return to_enum(:scan_each, options) unless block_given?
2449
+ cursor = 0
2450
+ loop do
2451
+ cursor, keys = scan(cursor, options)
2452
+ keys.each(&block)
2453
+ break if cursor == "0"
2454
+ end
2455
+ end
2456
+
2457
+ # Scan a hash
2458
+ #
2459
+ # @example Retrieve the first batch of key/value pairs in a hash
2460
+ # redis.hscan("hash", 0)
2461
+ #
2462
+ # @param [String, Integer] cursor the cursor of the iteration
2463
+ # @param [Hash] options
2464
+ # - `:match => String`: only return keys matching the pattern
2465
+ # - `:count => Integer`: return count keys at most per iteration
2466
+ #
2467
+ # @return [String, Array<[String, String]>] the next cursor and all found keys
2468
+ def hscan(key, cursor, options={})
2469
+ _scan(:hscan, cursor, [key], options) do |reply|
2470
+ [reply[0], _pairify(reply[1])]
2471
+ end
2472
+ end
2473
+
2474
+ # Scan a hash
2475
+ #
2476
+ # @example Retrieve all of the key/value pairs in a hash
2477
+ # redis.hscan_each("hash").to_a
2478
+ # # => [["key70", "70"], ["key80", "80"]]
2479
+ #
2480
+ # @param [Hash] options
2481
+ # - `:match => String`: only return keys matching the pattern
2482
+ # - `:count => Integer`: return count keys at most per iteration
2483
+ #
2484
+ # @return [Enumerator] an enumerator for all found keys
2485
+ def hscan_each(key, options={}, &block)
2486
+ return to_enum(:hscan_each, key, options) unless block_given?
2487
+ cursor = 0
2488
+ loop do
2489
+ cursor, values = hscan(key, cursor, options)
2490
+ values.each(&block)
2491
+ break if cursor == "0"
2492
+ end
2493
+ end
2494
+
2495
+ # Scan a sorted set
2496
+ #
2497
+ # @example Retrieve the first batch of key/value pairs in a hash
2498
+ # redis.zscan("zset", 0)
2499
+ #
2500
+ # @param [String, Integer] cursor the cursor of the iteration
2501
+ # @param [Hash] options
2502
+ # - `:match => String`: only return keys matching the pattern
2503
+ # - `:count => Integer`: return count keys at most per iteration
2504
+ #
2505
+ # @return [String, Array<[String, Float]>] the next cursor and all found
2506
+ # members and scores
2507
+ def zscan(key, cursor, options={})
2508
+ _scan(:zscan, cursor, [key], options) do |reply|
2509
+ [reply[0], _floatify_pairs.call(reply[1])]
2510
+ end
2511
+ end
2512
+
2513
+ # Scan a sorted set
2514
+ #
2515
+ # @example Retrieve all of the members/scores in a sorted set
2516
+ # redis.zscan_each("zset").to_a
2517
+ # # => [["key70", "70"], ["key80", "80"]]
2518
+ #
2519
+ # @param [Hash] options
2520
+ # - `:match => String`: only return keys matching the pattern
2521
+ # - `:count => Integer`: return count keys at most per iteration
2522
+ #
2523
+ # @return [Enumerator] an enumerator for all found scores and members
2524
+ def zscan_each(key, options={}, &block)
2525
+ return to_enum(:zscan_each, key, options) unless block_given?
2526
+ cursor = 0
2527
+ loop do
2528
+ cursor, values = zscan(key, cursor, options)
2529
+ values.each(&block)
2530
+ break if cursor == "0"
2531
+ end
2532
+ end
2533
+
2534
+ # Scan a set
2535
+ #
2536
+ # @example Retrieve the first batch of keys in a set
2537
+ # redis.sscan("set", 0)
2538
+ #
2539
+ # @param [String, Integer] cursor the cursor of the iteration
2540
+ # @param [Hash] options
2541
+ # - `:match => String`: only return keys matching the pattern
2542
+ # - `:count => Integer`: return count keys at most per iteration
2543
+ #
2544
+ # @return [String, Array<String>] the next cursor and all found members
2545
+ def sscan(key, cursor, options={})
2546
+ _scan(:sscan, cursor, [key], options)
2547
+ end
2548
+
2549
+ # Scan a set
2550
+ #
2551
+ # @example Retrieve all of the keys in a set
2552
+ # redis.sscan_each("set").to_a
2553
+ # # => ["key1", "key2", "key3"]
2554
+ #
2555
+ # @param [Hash] options
2556
+ # - `:match => String`: only return keys matching the pattern
2557
+ # - `:count => Integer`: return count keys at most per iteration
2558
+ #
2559
+ # @return [Enumerator] an enumerator for all keys in the set
2560
+ def sscan_each(key, options={}, &block)
2561
+ return to_enum(:sscan_each, key, options) unless block_given?
2562
+ cursor = 0
2563
+ loop do
2564
+ cursor, keys = sscan(key, cursor, options)
2565
+ keys.each(&block)
2566
+ break if cursor == "0"
2567
+ end
2568
+ end
2569
+
2570
+ # Add one or more members to a HyperLogLog structure.
2571
+ #
2572
+ # @param [String] key
2573
+ # @param [String, Array<String>] member one member, or array of members
2574
+ # @return [Boolean] true if at least 1 HyperLogLog internal register was altered. false otherwise.
2575
+ def pfadd(key, member)
2576
+ synchronize do |client|
2577
+ client.call([:pfadd, key, member], &_boolify)
2578
+ end
2579
+ end
2580
+
2581
+ # Get the approximate cardinality of members added to HyperLogLog structure.
2582
+ #
2583
+ # If called with multiple keys, returns the approximate cardinality of the
2584
+ # union of the HyperLogLogs contained in the keys.
2585
+ #
2586
+ # @param [String, Array<String>] keys
2587
+ # @return [Fixnum]
2588
+ def pfcount(*keys)
2589
+ synchronize do |client|
2590
+ client.call([:pfcount] + keys)
2591
+ end
2592
+ end
2593
+
2594
+ # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
2595
+ # the observed Sets of the source HyperLogLog structures.
2596
+ #
2597
+ # @param [String] dest_key destination key
2598
+ # @param [String, Array<String>] source_key source key, or array of keys
2599
+ # @return [Boolean]
2600
+ def pfmerge(dest_key, *source_key)
2601
+ synchronize do |client|
2602
+ client.call([:pfmerge, dest_key, *source_key], &_boolify_set)
2603
+ end
2604
+ end
2605
+
2606
+ # Interact with the sentinel command (masters, master, slaves, failover)
2607
+ #
2608
+ # @param [String] subcommand e.g. `masters`, `master`, `slaves`
2609
+ # @param [Array<String>] args depends on subcommand
2610
+ # @return [Array<String>, Hash<String, String>, String] depends on subcommand
2611
+ def sentinel(subcommand, *args)
2612
+ subcommand = subcommand.to_s.downcase
2613
+ synchronize do |client|
2614
+ client.call([:sentinel, subcommand] + args) do |reply|
2615
+ case subcommand
2616
+ when "get-master-addr-by-name"
2617
+ reply
2618
+ else
2619
+ if reply.kind_of?(Array)
2620
+ if reply[0].kind_of?(Array)
2621
+ reply.map(&_hashify)
2622
+ else
2623
+ _hashify.call(reply)
2624
+ end
2625
+ else
2626
+ reply
2627
+ end
2628
+ end
2629
+ end
2630
+ end
2631
+ end
2632
+
2633
+ def id
2634
+ @original_client.id
2635
+ end
2636
+
2637
+ def inspect
2638
+ "#<Redis client v#{Redis::VERSION} for #{id}>"
2639
+ end
2640
+
2641
+ def dup
2642
+ self.class.new(@options)
2643
+ end
2644
+
2645
+ def method_missing(command, *args)
2646
+ synchronize do |client|
2647
+ client.call([command] + args)
2648
+ end
2649
+ end
2650
+
2651
+ private
2652
+
2653
+ # Commands returning 1 for true and 0 for false may be executed in a pipeline
2654
+ # where the method call will return nil. Propagate the nil instead of falsely
2655
+ # returning false.
2656
+ def _boolify
2657
+ lambda { |value|
2658
+ value == 1 if value
2659
+ }
2660
+ end
2661
+
2662
+ def _boolify_set
2663
+ lambda { |value|
2664
+ if value && "OK" == value
2665
+ true
2666
+ else
2667
+ false
2668
+ end
2669
+ }
2670
+ end
2671
+
2672
+ def _hashify
2673
+ lambda { |array|
2674
+ hash = Hash.new
2675
+ array.each_slice(2) do |field, value|
2676
+ hash[field] = value
2677
+ end
2678
+ hash
2679
+ }
2680
+ end
2681
+
2682
+ def _floatify
2683
+ lambda { |str|
2684
+ return unless str
2685
+
2686
+ if (inf = str.match(/^(-)?inf/i))
2687
+ (inf[1] ? -1.0 : 1.0) / 0.0
2688
+ else
2689
+ Float(str)
2690
+ end
2691
+ }
2692
+ end
2693
+
2694
+ def _floatify_pairs
2695
+ lambda { |array|
2696
+ return unless array
2697
+
2698
+ array.each_slice(2).map do |member, score|
2699
+ [member, _floatify.call(score)]
2700
+ end
2701
+ }
2702
+ end
2703
+
2704
+ def _pairify(array)
2705
+ array.each_slice(2).to_a
2706
+ end
2707
+
2708
+ def _identity
2709
+ lambda { |value|
2710
+ value
2711
+ }
2712
+ end
2713
+
2714
+ def _subscription(method, channels, block)
2715
+ return @client.call([method] + channels) if subscribed?
2716
+
2717
+ begin
2718
+ original, @client = @client, SubscribedClient.new(@client)
2719
+ @client.send(method, *channels, &block)
2720
+ ensure
2721
+ @client = original
2722
+ end
2723
+ end
2724
+
2725
+ end
2726
+
2727
+ require "redis/version"
2728
+ require "redis/connection"
2729
+ require "redis/client"
2730
+ require "redis/pipeline"
2731
+ require "redis/subscribe"