redis2-namespaced 3.0.7

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