discourse-redis 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
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"