redis2-namespaced 3.0.7

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