redis 2.2.2 → 3.0.0.rc1

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 (61) hide show
  1. data/.gitignore +2 -0
  2. data/.yardopts +3 -0
  3. data/CHANGELOG.md +65 -1
  4. data/README.md +6 -0
  5. data/Rakefile +19 -27
  6. data/lib/redis.rb +737 -170
  7. data/lib/redis/client.rb +82 -67
  8. data/lib/redis/connection/command_helper.rb +15 -16
  9. data/lib/redis/connection/hiredis.rb +6 -3
  10. data/lib/redis/connection/ruby.rb +2 -1
  11. data/lib/redis/connection/synchrony.rb +3 -1
  12. data/lib/redis/distributed.rb +20 -18
  13. data/lib/redis/errors.rb +38 -0
  14. data/lib/redis/hash_ring.rb +2 -2
  15. data/lib/redis/pipeline.rb +91 -19
  16. data/lib/redis/subscribe.rb +1 -16
  17. data/lib/redis/version.rb +1 -1
  18. data/redis.gemspec +30 -11
  19. data/test/command_map_test.rb +29 -0
  20. data/test/commands_on_hashes_test.rb +3 -3
  21. data/test/commands_on_lists_test.rb +1 -1
  22. data/test/commands_on_sets_test.rb +0 -2
  23. data/test/commands_on_sorted_sets_test.rb +8 -9
  24. data/test/commands_on_strings_test.rb +3 -3
  25. data/test/commands_on_value_types_test.rb +0 -1
  26. data/test/connection_handling_test.rb +120 -4
  27. data/test/distributed_commands_on_hashes_test.rb +0 -1
  28. data/test/distributed_commands_on_lists_test.rb +0 -1
  29. data/test/distributed_commands_on_sets_test.rb +0 -1
  30. data/test/distributed_commands_on_sorted_sets_test.rb +19 -0
  31. data/test/distributed_commands_on_strings_test.rb +0 -1
  32. data/test/distributed_commands_on_value_types_test.rb +0 -1
  33. data/test/distributed_connection_handling_test.rb +0 -1
  34. data/test/distributed_key_tags_test.rb +0 -1
  35. data/test/distributed_persistence_control_commands_test.rb +0 -1
  36. data/test/distributed_publish_subscribe_test.rb +1 -2
  37. data/test/distributed_remote_server_control_commands_test.rb +2 -3
  38. data/test/distributed_transactions_test.rb +0 -1
  39. data/test/encoding_test.rb +0 -1
  40. data/test/helper.rb +14 -4
  41. data/test/helper_test.rb +8 -0
  42. data/test/internals_test.rb +25 -33
  43. data/test/lint/hashes.rb +17 -3
  44. data/test/lint/internals.rb +2 -3
  45. data/test/lint/lists.rb +17 -3
  46. data/test/lint/sets.rb +30 -6
  47. data/test/lint/sorted_sets.rb +56 -27
  48. data/test/lint/strings.rb +9 -13
  49. data/test/lint/value_types.rb +12 -15
  50. data/test/persistence_control_commands_test.rb +0 -1
  51. data/test/pipelining_commands_test.rb +69 -6
  52. data/test/publish_subscribe_test.rb +1 -1
  53. data/test/redis_mock.rb +14 -5
  54. data/test/remote_server_control_commands_test.rb +8 -2
  55. data/test/sorting_test.rb +0 -1
  56. data/test/test.conf +1 -0
  57. data/test/transactions_test.rb +88 -15
  58. data/test/unknown_commands_test.rb +1 -2
  59. data/test/url_param_test.rb +0 -1
  60. metadata +68 -16
  61. data/lib/redis/compat.rb +0 -21
data/.gitignore CHANGED
@@ -6,3 +6,5 @@ coverage/*
6
6
  .idea
7
7
  *.rdb
8
8
  *.swp
9
+ .yardoc
10
+ doc/
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --exclude redis/connection
2
+ --exclude redis/compat
3
+ --markup markdown
data/CHANGELOG.md CHANGED
@@ -1,4 +1,68 @@
1
- # 2.2.2 (unreleased)
1
+ # 3.0 (unreleased)
2
+
3
+ * The `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE` and `ZREVRANGEBYSCORE` commands
4
+ now return an array containing `[String, Float]` pairs when
5
+ `:with_scores => true` is passed.
6
+
7
+ * The `ZINCRBY` and `ZSCORE` commands now return a `Float` score instead
8
+ of a string holding a representation of the score.
9
+
10
+ * The client now raises custom exceptions where it makes sense.
11
+
12
+ If by any chance you were rescuing low-level exceptions (`Errno::*`),
13
+ you should now rescue as follows:
14
+
15
+ Errno::ECONNRESET -> Redis::ConnectionError
16
+ Errno::EPIPE -> Redis::ConnectionError
17
+ Errno::ECONNABORTED -> Redis::ConnectionError
18
+ Errno::EBADF -> Redis::ConnectionError
19
+ Errno::EINVAL -> Redis::ConnectionError
20
+ Errno::EAGAIN -> Redis::TimeoutError
21
+ Errno::ECONNREFUSED -> Redis::CannotConnectError
22
+
23
+ * Always raise exceptions originating from erroneous command invocation
24
+ inside pipelines and MULTI/EXEC blocks.
25
+
26
+ The old behavior (swallowing exceptions) could cause application bugs
27
+ to go unnoticed.
28
+
29
+ * Implement futures for assigning values inside pipelines and MULTI/EXEC
30
+ blocks. Futures are assigned their value after the pipeline or
31
+ MULTI/EXEC block has executed.
32
+
33
+ ```ruby
34
+ $redis.pipelined do
35
+ @future = $redis.get "key"
36
+ end
37
+
38
+ puts @future.value
39
+ ```
40
+
41
+ * Ruby 1.8.6 is officially not supported.
42
+
43
+ * Support `ZCOUNT` in `Redis::Distributed` (Michael Dungan).
44
+
45
+ * Pipelined commands now return the same replies as when called outside
46
+ a pipeline.
47
+
48
+ In the past, pipelined replies were returned without post-processing.
49
+
50
+ * Support `SLOWLOG` command (Michael Bernstein).
51
+
52
+ * Calling `SHUTDOWN` effectively disconnects the client (Stefan Kaes).
53
+
54
+ * Basic support for mapping commands so that they can be renamed on the
55
+ server.
56
+
57
+ * Connecting using a URL now checks that a host is given.
58
+
59
+ It's just a small sanity check, cf. #126
60
+
61
+ * Support variadic commands introduced in Redis 2.4.
62
+
63
+ # 2.2.2
64
+
65
+ * Added method `Redis::Distributed#hsetnx`.
2
66
 
3
67
  # 2.2.1
4
68
 
data/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  A Ruby client library for the [Redis](http://redis.io) key-value store.
4
4
 
5
+ A simple Ruby client trying to match Redis' API one-to-one while still providing a Rubystic interface.
6
+ It features thread safety, client-side sharding, and an obsession for performance.
7
+
5
8
  ## A note about versions
6
9
 
7
10
  Versions *1.0.x* target all versions of Redis. You have to use this one if you are using Redis < 1.2.
@@ -100,6 +103,9 @@ All users and admins:
100
103
 
101
104
  Redis only stores strings as values. If you want to store an object inside a key, you can use a serialization/deseralization mechanism like JSON:
102
105
 
106
+ >> require 'json'
107
+ => true
108
+
103
109
  >> redis.set "foo", [1, 2, 3].to_json
104
110
  => OK
105
111
 
data/Rakefile CHANGED
@@ -35,11 +35,6 @@ task :stop do
35
35
  end
36
36
  end
37
37
 
38
- def isolated(&block)
39
- pid = fork { yield }
40
- Process.wait(pid)
41
- end
42
-
43
38
  desc "Run the test suite"
44
39
  task :test => ["test:ruby", "test:hiredis", "test:synchrony"]
45
40
 
@@ -48,26 +43,23 @@ namespace :test do
48
43
  task :ruby do
49
44
  require "cutest"
50
45
 
51
- isolated do
52
- Cutest.run(Dir["./test/**/*_test.rb"])
53
- end
46
+ Cutest.run(Dir["./test/**/*_test.rb"])
54
47
  end
55
48
 
56
49
  desc "Run tests against the hiredis driver"
57
50
  task :hiredis do
58
51
  require "cutest"
59
52
 
60
- isolated do
61
- begin
62
- require "redis/connection/hiredis"
53
+ begin
54
+ require "redis/connection/hiredis"
63
55
 
64
- puts
65
- puts "Running tests against hiredis v#{Hiredis::VERSION}"
56
+ puts
57
+ puts "Running tests against hiredis v#{Hiredis::VERSION}"
66
58
 
67
- Cutest.run(Dir["./test/**/*_test.rb"])
68
- rescue LoadError
69
- puts "Skipping tests against hiredis"
70
- end
59
+ ENV["REDIS_CONNECTION_DRIVER"] = "hiredis"
60
+ Cutest.run(Dir["./test/**/*_test.rb"])
61
+ rescue LoadError
62
+ puts "Skipping tests against hiredis"
71
63
  end
72
64
  end
73
65
 
@@ -78,18 +70,18 @@ namespace :test do
78
70
  # Synchrony needs 1.9
79
71
  next if RUBY_VERSION < "1.9"
80
72
 
81
- isolated do
82
- begin
83
- require "redis/connection/synchrony"
73
+ begin
74
+ require "redis/connection/synchrony"
84
75
 
85
- puts
86
- puts "Running tests against em-synchrony"
76
+ puts
77
+ puts "Running tests against em-synchrony"
87
78
 
88
- threaded_tests = ['./test/thread_safety_test.rb']
89
- Cutest.run(Dir['./test/**/*_test.rb'] - threaded_tests)
90
- rescue LoadError
91
- puts "Skipping tests against em-synchrony"
92
- end
79
+ threaded_tests = ['./test/thread_safety_test.rb']
80
+
81
+ ENV["REDIS_CONNECTION_DRIVER"] = "synchrony"
82
+ Cutest.run(Dir['./test/**/*_test.rb'] - threaded_tests)
83
+ rescue LoadError
84
+ puts "Skipping tests against em-synchrony"
93
85
  end
94
86
  end
95
87
  end
data/lib/redis.rb CHANGED
@@ -1,23 +1,7 @@
1
1
  require "monitor"
2
+ require "redis/errors"
2
3
 
3
4
  class Redis
4
- class ProtocolError < RuntimeError
5
- def initialize(reply_type)
6
- super(<<-EOS.gsub(/(?:^|\n)\s*/, " "))
7
- Got '#{reply_type}' as initial reply byte.
8
- If you're running in a multi-threaded environment, make sure you
9
- pass the :thread_safe option when initializing the connection.
10
- If you're in a forking environment, such as Unicorn, you need to
11
- connect to Redis after forking.
12
- EOS
13
- end
14
- end
15
-
16
- module DisableThreadSafety
17
- def synchronize
18
- yield
19
- end
20
- end
21
5
 
22
6
  def self.deprecate(message, trace = caller[0])
23
7
  $stderr.puts "\n#{message} (in #{trace})"
@@ -28,14 +12,20 @@ class Redis
28
12
  def self.connect(options = {})
29
13
  options = options.dup
30
14
 
31
- require "uri"
15
+ url = options.delete(:url) || ENV["REDIS_URL"]
16
+ if url
17
+ require "uri"
18
+
19
+ uri = URI(url)
32
20
 
33
- url = URI(options.delete(:url) || ENV["REDIS_URL"] || "redis://127.0.0.1:6379/0")
21
+ # Require the URL to have at least a host
22
+ raise ArgumentError, "invalid url" unless uri.host
34
23
 
35
- options[:host] ||= url.host
36
- options[:port] ||= url.port
37
- options[:password] ||= url.password
38
- options[:db] ||= url.path[1..-1].to_i
24
+ options[:host] ||= uri.host
25
+ options[:port] ||= uri.port
26
+ options[:password] ||= uri.password
27
+ options[:db] ||= uri.path[1..-1].to_i
28
+ end
39
29
 
40
30
  new(options)
41
31
  end
@@ -54,14 +44,17 @@ class Redis
54
44
  @client = Client.new(options)
55
45
 
56
46
  if options[:thread_safe] == false
57
- # Override #synchronize
58
- extend DisableThreadSafety
47
+ @synchronizer = lambda { |&block| block.call }
59
48
  else
60
- # Monitor#initialize
61
- super()
49
+ @synchronizer = lambda { |&block| mon_synchronize { block.call } }
50
+ super() # Monitor#initialize
62
51
  end
63
52
  end
64
53
 
54
+ def synchronize
55
+ @synchronizer.call { yield }
56
+ end
57
+
65
58
  # Run code without the client reconnecting
66
59
  def without_reconnect(&block)
67
60
  synchronize do
@@ -70,6 +63,10 @@ class Redis
70
63
  end
71
64
 
72
65
  # Authenticate to the server.
66
+ #
67
+ # @param [String] password must match the password specified in the
68
+ # `requirepass` directive in the configuration file
69
+ # @return [String] `OK`
73
70
  def auth(password)
74
71
  synchronize do
75
72
  @client.call [:auth, password]
@@ -77,6 +74,9 @@ class Redis
77
74
  end
78
75
 
79
76
  # Change the selected database for the current connection.
77
+ #
78
+ # @param [Fixnum] db zero-based index of the DB to use (0 to 15)
79
+ # @return [String] `OK`
80
80
  def select(db)
81
81
  synchronize do
82
82
  @client.db = db
@@ -85,38 +85,48 @@ class Redis
85
85
  end
86
86
 
87
87
  # Get information and statistics about the server.
88
+ #
89
+ # @param [String, Symbol] cmd e.g. "commandstats"
90
+ # @return [Hash<String, String>]
88
91
  def info(cmd = nil)
89
92
  synchronize do
90
- reply = @client.call [:info, cmd].compact
91
-
92
- if reply.kind_of?(String)
93
- reply = Hash[*reply.split(/:|\r\n/).grep(/^[^#]/)]
93
+ @client.call [:info, cmd].compact do |reply|
94
+ if reply.kind_of?(String)
95
+ reply = Hash[*reply.split(/:|\r\n/).grep(/^[^#]/)]
94
96
 
95
- if cmd && cmd.to_s == "commandstats"
96
- # Extract nested hashes for INFO COMMANDSTATS
97
- reply = Hash[reply.map do |k, v|
98
- [k[/^cmdstat_(.*)$/, 1], Hash[*v.split(/,|=/)]]
99
- end]
97
+ if cmd && cmd.to_s == "commandstats"
98
+ # Extract nested hashes for INFO COMMANDSTATS
99
+ reply = Hash[reply.map do |k, v|
100
+ [k[/^cmdstat_(.*)$/, 1], Hash[*v.split(/,|=/)]]
101
+ end]
102
+ end
100
103
  end
101
- end
102
104
 
103
- reply
105
+ reply
106
+ end
104
107
  end
105
108
  end
106
109
 
110
+ # Get or set server configuration parameters.
111
+ #
112
+ # @param [String] action e.g. `get`, `set`, `resetstat`
113
+ # @return [String, Hash] string reply, or hash when retrieving more than one
114
+ # property with `CONFIG GET`
107
115
  def config(action, *args)
108
116
  synchronize do
109
- reply = @client.call [:config, action, *args]
110
-
111
- if reply.kind_of?(Array) && action == :get
112
- Hash[*reply]
113
- else
114
- reply
117
+ @client.call [:config, action, *args] do |reply|
118
+ if reply.kind_of?(Array) && action == :get
119
+ Hash[*reply]
120
+ else
121
+ reply
122
+ end
115
123
  end
116
124
  end
117
125
  end
118
126
 
119
127
  # Remove all keys from the current database.
128
+ #
129
+ # @return [String] `OK`
120
130
  def flushdb
121
131
  synchronize do
122
132
  @client.call [:flushdb]
@@ -124,6 +134,8 @@ class Redis
124
134
  end
125
135
 
126
136
  # Remove all keys from all databases.
137
+ #
138
+ # @return [String] `OK`
127
139
  def flushall
128
140
  synchronize do
129
141
  @client.call [:flushall]
@@ -131,6 +143,8 @@ class Redis
131
143
  end
132
144
 
133
145
  # Synchronously save the dataset to disk.
146
+ #
147
+ # @return [String]
134
148
  def save
135
149
  synchronize do
136
150
  @client.call [:save]
@@ -138,6 +152,8 @@ class Redis
138
152
  end
139
153
 
140
154
  # Asynchronously save the dataset to disk.
155
+ #
156
+ # @return [String] `OK`
141
157
  def bgsave
142
158
  synchronize do
143
159
  @client.call [:bgsave]
@@ -145,6 +161,8 @@ class Redis
145
161
  end
146
162
 
147
163
  # Asynchronously rewrite the append-only file.
164
+ #
165
+ # @return [String] `OK`
148
166
  def bgrewriteaof
149
167
  synchronize do
150
168
  @client.call [:bgrewriteaof]
@@ -152,13 +170,22 @@ class Redis
152
170
  end
153
171
 
154
172
  # Get the value of a key.
173
+ #
174
+ # @param [String] key
175
+ # @return [String]
155
176
  def get(key)
156
177
  synchronize do
157
178
  @client.call [:get, key]
158
179
  end
159
180
  end
160
181
 
182
+ alias :[] :get
183
+
161
184
  # Returns the bit value at offset in the string value stored at key.
185
+ #
186
+ # @param [String] key
187
+ # @param [Fixnum] offset bit offset
188
+ # @return [Fixnum] `0` or `1`
162
189
  def getbit(key, offset)
163
190
  synchronize do
164
191
  @client.call [:getbit, key, offset]
@@ -166,6 +193,12 @@ class Redis
166
193
  end
167
194
 
168
195
  # Get a substring of the string stored at a key.
196
+ #
197
+ # @param [String] key
198
+ # @param [Fixnum] start zero-based start offset
199
+ # @param [Fixnum] stop zero-based end offset. Use -1 for representing
200
+ # the end of the string
201
+ # @return [Fixnum] `0` or `1`
169
202
  def getrange(key, start, stop)
170
203
  synchronize do
171
204
  @client.call [:getrange, key, start, stop]
@@ -173,6 +206,11 @@ class Redis
173
206
  end
174
207
 
175
208
  # Set the string value of a key and return its old value.
209
+ #
210
+ # @param [String] key
211
+ # @param [String] value value to replace the current value with
212
+ # @return [String] the old value stored in the key, or `nil` if the key
213
+ # did not exist
176
214
  def getset(key, value)
177
215
  synchronize do
178
216
  @client.call [:getset, key, value]
@@ -180,26 +218,31 @@ class Redis
180
218
  end
181
219
 
182
220
  # Get the values of all the given keys.
183
- def mget(*keys)
221
+ #
222
+ # @param [Array<String>] keys
223
+ # @return [Array<String>]
224
+ def mget(*keys, &blk)
184
225
  synchronize do
185
- @client.call [:mget, *keys]
226
+ @client.call [:mget, *keys], &blk
186
227
  end
187
228
  end
188
229
 
189
230
  # Append a value to a key.
231
+ #
232
+ # @param [String] key
233
+ # @param [String] value value to append
234
+ # @return [Fixnum] length of the string after appending
190
235
  def append(key, value)
191
236
  synchronize do
192
237
  @client.call [:append, key, value]
193
238
  end
194
239
  end
195
240
 
196
- def substr(key, start, stop)
197
- synchronize do
198
- @client.call [:substr, key, start, stop]
199
- end
200
- end
201
-
202
241
  # Get the length of the value stored in a key.
242
+ #
243
+ # @param [String] key
244
+ # @return [Fixnum] the length of the value stored in the key, or 0
245
+ # if the key does not exist
203
246
  def strlen(key)
204
247
  synchronize do
205
248
  @client.call [:strlen, key]
@@ -207,26 +250,41 @@ class Redis
207
250
  end
208
251
 
209
252
  # Get all the fields and values in a hash.
253
+ #
254
+ # @param [String] key
255
+ # @return [Hash<String, String>]
210
256
  def hgetall(key)
211
257
  synchronize do
212
- reply = @client.call [:hgetall, key]
213
-
214
- if reply.kind_of?(Array)
215
- Hash[*reply]
216
- else
217
- reply
258
+ @client.call [:hgetall, key] do |reply|
259
+ if reply.kind_of?(Array)
260
+ hash = Hash.new
261
+ reply.each_slice(2) do |field, value|
262
+ hash[field] = value
263
+ end
264
+ hash
265
+ else
266
+ reply
267
+ end
218
268
  end
219
269
  end
220
270
  end
221
271
 
222
272
  # Get the value of a hash field.
273
+ #
274
+ # @param [String] key
275
+ # @param [String] field
276
+ # @return [String]
223
277
  def hget(key, field)
224
278
  synchronize do
225
279
  @client.call [:hget, key, field]
226
280
  end
227
281
  end
228
282
 
229
- # Delete a hash field.
283
+ # Delete one or more hash fields.
284
+ #
285
+ # @param [String] key
286
+ # @param [String, Array<String>] field
287
+ # @return [Fixnum] the number of fields that were removed from the hash
230
288
  def hdel(key, field)
231
289
  synchronize do
232
290
  @client.call [:hdel, key, field]
@@ -234,6 +292,9 @@ class Redis
234
292
  end
235
293
 
236
294
  # Get all the fields in a hash.
295
+ #
296
+ # @param [String] key
297
+ # @return [Array<String>]
237
298
  def hkeys(key)
238
299
  synchronize do
239
300
  @client.call [:hkeys, key]
@@ -241,19 +302,24 @@ class Redis
241
302
  end
242
303
 
243
304
  # Find all keys matching the given pattern.
305
+ #
306
+ # @param [String] pattern
307
+ # @return [Array<String>]
244
308
  def keys(pattern = "*")
245
309
  synchronize do
246
- reply = @client.call [:keys, pattern]
247
-
248
- if reply.kind_of?(String)
249
- reply.split(" ")
250
- else
251
- reply
310
+ @client.call [:keys, pattern] do |reply|
311
+ if reply.kind_of?(String)
312
+ reply.split(" ")
313
+ else
314
+ reply
315
+ end
252
316
  end
253
317
  end
254
318
  end
255
319
 
256
320
  # Return a random key from the keyspace.
321
+ #
322
+ # @return [String]
257
323
  def randomkey
258
324
  synchronize do
259
325
  @client.call [:randomkey]
@@ -261,6 +327,9 @@ class Redis
261
327
  end
262
328
 
263
329
  # Echo the given string.
330
+ #
331
+ # @param [String] value
332
+ # @return [String]
264
333
  def echo(value)
265
334
  synchronize do
266
335
  @client.call [:echo, value]
@@ -268,6 +337,8 @@ class Redis
268
337
  end
269
338
 
270
339
  # Ping the server.
340
+ #
341
+ # @return [String] `PONG`
271
342
  def ping
272
343
  synchronize do
273
344
  @client.call [:ping]
@@ -275,6 +346,8 @@ class Redis
275
346
  end
276
347
 
277
348
  # Get the UNIX time stamp of the last successful save to disk.
349
+ #
350
+ # @return [Fixnum]
278
351
  def lastsave
279
352
  synchronize do
280
353
  @client.call [:lastsave]
@@ -282,6 +355,8 @@ class Redis
282
355
  end
283
356
 
284
357
  # Return the number of keys in the selected database.
358
+ #
359
+ # @return [Fixnum]
285
360
  def dbsize
286
361
  synchronize do
287
362
  @client.call [:dbsize]
@@ -289,13 +364,19 @@ class Redis
289
364
  end
290
365
 
291
366
  # Determine if a key exists.
367
+ #
368
+ # @param [String] key
369
+ # @return [Boolean]
292
370
  def exists(key)
293
371
  synchronize do
294
- _bool @client.call [:exists, key]
372
+ @client.call [:exists, key], &_boolify
295
373
  end
296
374
  end
297
375
 
298
376
  # Get the length of a list.
377
+ #
378
+ # @param [String] key
379
+ # @return [Fixnum]
299
380
  def llen(key)
300
381
  synchronize do
301
382
  @client.call [:llen, key]
@@ -303,6 +384,11 @@ class Redis
303
384
  end
304
385
 
305
386
  # Get a range of elements from a list.
387
+ #
388
+ # @param [String] key
389
+ # @param [Fixnum] start start index
390
+ # @param [Fixnum] stop stop index
391
+ # @return [Array<String>]
306
392
  def lrange(key, start, stop)
307
393
  synchronize do
308
394
  @client.call [:lrange, key, start, stop]
@@ -310,6 +396,11 @@ class Redis
310
396
  end
311
397
 
312
398
  # Trim a list to the specified range.
399
+ #
400
+ # @param [String] key
401
+ # @param [Fixnum] start start index
402
+ # @param [Fixnum] stop stop index
403
+ # @return [String] `OK`
313
404
  def ltrim(key, start, stop)
314
405
  synchronize do
315
406
  @client.call [:ltrim, key, start, stop]
@@ -317,6 +408,10 @@ class Redis
317
408
  end
318
409
 
319
410
  # Get an element from a list by its index.
411
+ #
412
+ # @param [String] key
413
+ # @param [Fixnum] index
414
+ # @return [String]
320
415
  def lindex(key, index)
321
416
  synchronize do
322
417
  @client.call [:lindex, key, index]
@@ -324,6 +419,13 @@ class Redis
324
419
  end
325
420
 
326
421
  # Insert an element before or after another element in a list.
422
+ #
423
+ # @param [String] key
424
+ # @param [String, Symbol] where `BEFORE` or `AFTER`
425
+ # @param [String] pivot reference element
426
+ # @param [String] value
427
+ # @return [Fixnum] length of the list after the insert operation, or `-1`
428
+ # when the element `pivot` was not found
327
429
  def linsert(key, where, pivot, value)
328
430
  synchronize do
329
431
  @client.call [:linsert, key, where, pivot, value]
@@ -331,6 +433,11 @@ class Redis
331
433
  end
332
434
 
333
435
  # Set the value of an element in a list by its index.
436
+ #
437
+ # @param [String] key
438
+ # @param [Fixnum] index
439
+ # @param [String] value
440
+ # @return [String] `OK`
334
441
  def lset(key, index, value)
335
442
  synchronize do
336
443
  @client.call [:lset, key, index, value]
@@ -338,13 +445,25 @@ class Redis
338
445
  end
339
446
 
340
447
  # Remove elements from a list.
448
+ #
449
+ # @param [String] key
450
+ # @param [Fixnum] count number of elements to remove. Use a positive
451
+ # value to remove the first `count` occurrences of `value`. A negative
452
+ # value to remove the last `count` occurrences of `value`. Or zero, to
453
+ # remove all occurrences of `value` from the list.
454
+ # @param [String] value
455
+ # @return [Fixnum] the number of removed elements
341
456
  def lrem(key, count, value)
342
457
  synchronize do
343
458
  @client.call [:lrem, key, count, value]
344
459
  end
345
460
  end
346
461
 
347
- # Append a value to a list.
462
+ # Append one or more values to a list, creating the list if it doesn't exist
463
+ #
464
+ # @param [String] key
465
+ # @param [String] value
466
+ # @return [Fixnum] the length of the list after the push operation
348
467
  def rpush(key, value)
349
468
  synchronize do
350
469
  @client.call [:rpush, key, value]
@@ -352,13 +471,21 @@ class Redis
352
471
  end
353
472
 
354
473
  # Append a value to a list, only if the list exists.
474
+ #
475
+ # @param [String] key
476
+ # @param [String] value
477
+ # @return [Fixnum] the length of the list after the push operation
355
478
  def rpushx(key, value)
356
479
  synchronize do
357
480
  @client.call [:rpushx, key, value]
358
481
  end
359
482
  end
360
483
 
361
- # Prepend a value to a list.
484
+ # Prepend one or more values to a list, creating the list if it doesn't exist
485
+ #
486
+ # @param [String] key
487
+ # @param [String] value
488
+ # @return [Fixnum] the length of the list after the push operation
362
489
  def lpush(key, value)
363
490
  synchronize do
364
491
  @client.call [:lpush, key, value]
@@ -366,6 +493,10 @@ class Redis
366
493
  end
367
494
 
368
495
  # Prepend a value to a list, only if the list exists.
496
+ #
497
+ # @param [String] key
498
+ # @param [String] value
499
+ # @return [Fixnum] the length of the list after the push operation
369
500
  def lpushx(key, value)
370
501
  synchronize do
371
502
  @client.call [:lpushx, key, value]
@@ -373,6 +504,9 @@ class Redis
373
504
  end
374
505
 
375
506
  # Remove and get the last element in a list.
507
+ #
508
+ # @param [String] key
509
+ # @return [String]
376
510
  def rpop(key)
377
511
  synchronize do
378
512
  @client.call [:rpop, key]
@@ -380,28 +514,47 @@ class Redis
380
514
  end
381
515
 
382
516
  # Remove and get the first element in a list, or block until one is available.
517
+ #
518
+ # @param [Array<String>] args one or more keys to perform a blocking pop on,
519
+ # followed by a `Fixnum` timeout value
520
+ # @return [nil, Array<String>] tuple of list that was popped from and element
521
+ # that was popped, or nil when the blocking operation timed out
383
522
  def blpop(*args)
384
523
  synchronize do
385
- @client.call_without_timeout(:blpop, *args)
524
+ @client.call_without_timeout [:blpop, *args]
386
525
  end
387
526
  end
388
527
 
389
528
  # Remove and get the last element in a list, or block until one is available.
529
+ #
530
+ # @param [Array<String>] args one or more keys to perform a blocking pop on,
531
+ # followed by a `Fixnum` timeout value
532
+ # @return [nil, Array<String>] tuple of list that was popped from and element
533
+ # that was popped, or nil when the blocking operation timed out
390
534
  def brpop(*args)
391
535
  synchronize do
392
- @client.call_without_timeout(:brpop, *args)
536
+ @client.call_without_timeout [:brpop, *args]
393
537
  end
394
538
  end
395
539
 
396
540
  # Pop a value from a list, push it to another list and return it; or block
397
541
  # until one is available.
542
+ #
543
+ # @param [String] source source key
544
+ # @param [String] destination destination key
545
+ # @param [Fixnum] timeout
546
+ # @return [nil, String] the element, or nil when the blocking operation timed out
398
547
  def brpoplpush(source, destination, timeout)
399
548
  synchronize do
400
- @client.call_without_timeout(:brpoplpush, source, destination, timeout)
549
+ @client.call_without_timeout [:brpoplpush, source, destination, timeout]
401
550
  end
402
551
  end
403
552
 
404
553
  # Remove the last element in a list, append it to another list and return it.
554
+ #
555
+ # @param [String] source source key
556
+ # @param [String] destination destination key
557
+ # @return [nil, String] the element, or nil when the source key does not exist
405
558
  def rpoplpush(source, destination)
406
559
  synchronize do
407
560
  @client.call [:rpoplpush, source, destination]
@@ -409,13 +562,32 @@ class Redis
409
562
  end
410
563
 
411
564
  # Remove and get the first element in a list.
565
+ #
566
+ # @param [String] key
567
+ # @return [String]
412
568
  def lpop(key)
413
569
  synchronize do
414
570
  @client.call [:lpop, key]
415
571
  end
416
572
  end
417
573
 
574
+ # Interact with the slowlog (get, len, reset)
575
+ #
576
+ # @param [String] subcommand e.g. `get`, `len`, `reset`
577
+ # @param [Fixnum] length maximum number of entries to return
578
+ # @return [Array<String>, Fixnum, String] depends on subcommand
579
+ def slowlog(subcommand, length=nil)
580
+ synchronize do
581
+ args = [:slowlog, subcommand]
582
+ args << length if length
583
+ @client.call args
584
+ end
585
+ end
586
+
418
587
  # Get all the members in a set.
588
+ #
589
+ # @param [String] key
590
+ # @return [Array<String>]
419
591
  def smembers(key)
420
592
  synchronize do
421
593
  @client.call [:smembers, key]
@@ -423,34 +595,76 @@ class Redis
423
595
  end
424
596
 
425
597
  # Determine if a given value is a member of a set.
598
+ #
599
+ # @param [String] key
600
+ # @param [String] member
601
+ # @return [Boolean]
426
602
  def sismember(key, member)
427
603
  synchronize do
428
- _bool @client.call [:sismember, key, member]
604
+ @client.call [:sismember, key, member], &_boolify
429
605
  end
430
606
  end
431
607
 
432
- # Add a member to a set.
433
- def sadd(key, value)
608
+ # Add one or more members to a set.
609
+ #
610
+ # @param [String] key
611
+ # @param [String, Array<String>] member one member, or array of members
612
+ # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
613
+ # holding whether or not adding the member succeeded, or `Fixnum` when an
614
+ # array of members is specified, holding the number of members that were
615
+ # successfully added
616
+ def sadd(key, member)
434
617
  synchronize do
435
- _bool @client.call [:sadd, key, value]
618
+ @client.call [:sadd, key, member] do |reply|
619
+ if member.is_a? Array
620
+ # Variadic: return integer
621
+ reply
622
+ else
623
+ # Single argument: return boolean
624
+ _boolify.call(reply)
625
+ end
626
+ end
436
627
  end
437
628
  end
438
629
 
439
- # Remove a member from a set.
440
- def srem(key, value)
441
- synchronize do
442
- _bool @client.call [:srem, key, value]
630
+ # Remove one or more members from a set.
631
+ #
632
+ # @param [String] key
633
+ # @param [String, Array<String>] member one member, or array of members
634
+ # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
635
+ # holding whether or not removing the member succeeded, or `Fixnum` when an
636
+ # array of members is specified, holding the number of members that were
637
+ # successfully removed
638
+ def srem(key, member)
639
+ synchronize do
640
+ @client.call [:srem, key, member] do |reply|
641
+ if member.is_a? Array
642
+ # Variadic: return integer
643
+ reply
644
+ else
645
+ # Single argument: return boolean
646
+ _boolify.call(reply)
647
+ end
648
+ end
443
649
  end
444
650
  end
445
651
 
446
652
  # Move a member from one set to another.
653
+ #
654
+ # @param [String] source source key
655
+ # @param [String] destination destination key
656
+ # @param [String] member member to move from `source` to `destination`
657
+ # @return [Boolean]
447
658
  def smove(source, destination, member)
448
659
  synchronize do
449
- _bool @client.call [:smove, source, destination, member]
660
+ @client.call [:smove, source, destination, member], &_boolify
450
661
  end
451
662
  end
452
663
 
453
664
  # Remove and return a random member from a set.
665
+ #
666
+ # @param [String] key
667
+ # @return [String]
454
668
  def spop(key)
455
669
  synchronize do
456
670
  @client.call [:spop, key]
@@ -458,6 +672,9 @@ class Redis
458
672
  end
459
673
 
460
674
  # Get the number of members in a set.
675
+ #
676
+ # @param [String] key
677
+ # @return [Fixnum]
461
678
  def scard(key)
462
679
  synchronize do
463
680
  @client.call [:scard, key]
@@ -465,6 +682,9 @@ class Redis
465
682
  end
466
683
 
467
684
  # Intersect multiple sets.
685
+ #
686
+ # @param [String, Array<String>] keys keys pointing to sets to intersect
687
+ # @return [Array<String>] members in the intersection
468
688
  def sinter(*keys)
469
689
  synchronize do
470
690
  @client.call [:sinter, *keys]
@@ -472,6 +692,10 @@ class Redis
472
692
  end
473
693
 
474
694
  # Intersect multiple sets and store the resulting set in a key.
695
+ #
696
+ # @param [String] destination destination key
697
+ # @param [String, Array<String>] keys keys pointing to sets to intersect
698
+ # @return [Fixnum] number of elements in the resulting set
475
699
  def sinterstore(destination, *keys)
476
700
  synchronize do
477
701
  @client.call [:sinterstore, destination, *keys]
@@ -479,6 +703,9 @@ class Redis
479
703
  end
480
704
 
481
705
  # Add multiple sets.
706
+ #
707
+ # @param [String, Array<String>] keys keys pointing to sets to unify
708
+ # @return [Array<String>] members in the union
482
709
  def sunion(*keys)
483
710
  synchronize do
484
711
  @client.call [:sunion, *keys]
@@ -486,6 +713,10 @@ class Redis
486
713
  end
487
714
 
488
715
  # Add multiple sets and store the resulting set in a key.
716
+ #
717
+ # @param [String] destination destination key
718
+ # @param [String, Array<String>] keys keys pointing to sets to unify
719
+ # @return [Fixnum] number of elements in the resulting set
489
720
  def sunionstore(destination, *keys)
490
721
  synchronize do
491
722
  @client.call [:sunionstore, destination, *keys]
@@ -493,6 +724,9 @@ class Redis
493
724
  end
494
725
 
495
726
  # Subtract multiple sets.
727
+ #
728
+ # @param [String, Array<String>] keys keys pointing to sets to subtract
729
+ # @return [Array<String>] members in the difference
496
730
  def sdiff(*keys)
497
731
  synchronize do
498
732
  @client.call [:sdiff, *keys]
@@ -500,6 +734,10 @@ class Redis
500
734
  end
501
735
 
502
736
  # Subtract multiple sets and store the resulting set in a key.
737
+ #
738
+ # @param [String] destination destination key
739
+ # @param [String, Array<String>] keys keys pointing to sets to subtract
740
+ # @return [Fixnum] number of elements in the resulting set
503
741
  def sdiffstore(destination, *keys)
504
742
  synchronize do
505
743
  @client.call [:sdiffstore, destination, *keys]
@@ -507,20 +745,83 @@ class Redis
507
745
  end
508
746
 
509
747
  # Get a random member from a set.
748
+ #
749
+ # @param [String] key
750
+ # @return [String]
510
751
  def srandmember(key)
511
752
  synchronize do
512
753
  @client.call [:srandmember, key]
513
754
  end
514
755
  end
515
756
 
516
- # Add a member to a sorted set, or update its score if it already exists.
517
- def zadd(key, score, member)
757
+ # Add one or more members to a sorted set, or update the score for members
758
+ # that already exist.
759
+ #
760
+ # @example Add a single `(score, member)` pair to a sorted set
761
+ # redis.zadd("zset", 32.0, "member")
762
+ # @example Add an array of `(score, member)` pairs to a sorted set
763
+ # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]])
764
+ #
765
+ # @param [String] key
766
+ # @param [(Float, String), Array<(Float,String)>] args
767
+ # - a single `(score, member)` pair
768
+ # - an array of `(score, member)` pairs
769
+ #
770
+ # @return [Boolean, Fixnum]
771
+ # - `Boolean` when a single pair is specified, holding whether or not it was
772
+ # **added** to the sorted set
773
+ # - `Fixnum` when an array of pairs is specified, holding the number of
774
+ # pairs that were **added** to the sorted set
775
+ def zadd(key, *args)
776
+ synchronize do
777
+ if args.size == 1 && args[0].is_a?(Array)
778
+ # Variadic: return integer
779
+ @client.call [:zadd, key] + args[0]
780
+ elsif args.size == 2
781
+ # Single pair: return boolean
782
+ @client.call [:zadd, key, args[0], args[1]], &_boolify
783
+ else
784
+ raise ArgumentError, "wrong number of arguments"
785
+ end
786
+ end
787
+ end
788
+
789
+ # Remove one or more members from a sorted set.
790
+ #
791
+ # @example Remove a single member from a sorted set
792
+ # redis.zrem("zset", "a")
793
+ # @example Remove an array of members from a sorted set
794
+ # redis.zrem("zset", ["a", "b"])
795
+ #
796
+ # @param [String] key
797
+ # @param [String, Array<String>] member
798
+ # - a single member
799
+ # - an array of members
800
+ #
801
+ # @return [Boolean, Fixnum]
802
+ # - `Boolean` when a single member is specified, holding whether or not it
803
+ # was removed from the sorted set
804
+ # - `Fixnum` when an array of pairs is specified, holding the number of
805
+ # members that were removed to the sorted set
806
+ def zrem(key, member)
518
807
  synchronize do
519
- _bool @client.call [:zadd, key, score, member]
808
+ @client.call [:zrem, key, member] do |reply|
809
+ if member.is_a? Array
810
+ # Variadic: return integer
811
+ reply
812
+ else
813
+ # Single argument: return boolean
814
+ _boolify.call(reply)
815
+ end
816
+ end
520
817
  end
521
818
  end
522
819
 
523
820
  # Determine the index of a member in a sorted set.
821
+ #
822
+ # @param [String] key
823
+ # @param [String] member
824
+ # @return [Fixnum]
524
825
  def zrank(key, member)
525
826
  synchronize do
526
827
  @client.call [:zrank, key, member]
@@ -529,6 +830,10 @@ class Redis
529
830
 
530
831
  # Determine the index of a member in a sorted set, with scores ordered from
531
832
  # high to low.
833
+ #
834
+ # @param [String] key
835
+ # @param [String] member
836
+ # @return [Fixnum]
532
837
  def zrevrank(key, member)
533
838
  synchronize do
534
839
  @client.call [:zrevrank, key, member]
@@ -536,13 +841,31 @@ class Redis
536
841
  end
537
842
 
538
843
  # Increment the score of a member in a sorted set.
844
+ #
845
+ # @example
846
+ # redis.zincrby("zset", 32.0, "a")
847
+ # # => 64.0
848
+ #
849
+ # @param [String] key
850
+ # @param [Float] increment
851
+ # @param [String] member
852
+ # @return [Float] score of the member after incrementing it
539
853
  def zincrby(key, increment, member)
540
854
  synchronize do
541
- @client.call [:zincrby, key, increment, member]
855
+ @client.call [:zincrby, key, increment, member] do |reply|
856
+ Float(reply) if reply
857
+ end
542
858
  end
543
859
  end
544
860
 
545
861
  # Get the number of members in a sorted set.
862
+ #
863
+ # @example
864
+ # redis.zcard("zset")
865
+ # # => 4
866
+ #
867
+ # @param [String] key
868
+ # @return [Fixnum]
546
869
  def zcard(key)
547
870
  synchronize do
548
871
  @client.call [:zcard, key]
@@ -550,65 +873,205 @@ class Redis
550
873
  end
551
874
 
552
875
  # Return a range of members in a sorted set, by index.
876
+ #
877
+ # @example Retrieve all members from a sorted set
878
+ # redis.zrange("zset", 0, -1)
879
+ # # => ["a", "b"]
880
+ # @example Retrieve all members and their scores from a sorted set
881
+ # redis.zrange("zset", 0, -1, :with_scores => true)
882
+ # # => [["a", 32.0], ["b", 64.0]]
883
+ #
884
+ # @param [String] key
885
+ # @param [Fixnum] start start index
886
+ # @param [Fixnum] stop stop index
887
+ # @param [Hash] options
888
+ # - `:with_scores => true`: include scores in output
889
+ #
890
+ # @return [Array<String>, Array<(String, Float)>]
891
+ # - when `:with_scores` is not specified, an array of members
892
+ # - when `:with_scores` is specified, an array with `(member, score)` pairs
553
893
  def zrange(key, start, stop, options = {})
554
- command = CommandOptions.new(options) do |c|
555
- c.bool :withscores
556
- c.bool :with_scores
557
- end
894
+ args = []
895
+
896
+ with_scores = options[:with_scores] || options[:withscores]
897
+ args << "WITHSCORES" if with_scores
558
898
 
559
899
  synchronize do
560
- @client.call [:zrange, key, start, stop, *command.to_a]
900
+ @client.call [:zrange, key, start, stop, *args] do |reply|
901
+ if with_scores
902
+ if reply
903
+ reply.each_slice(2).map do |member, score|
904
+ [member, Float(score)]
905
+ end
906
+ end
907
+ else
908
+ reply
909
+ end
910
+ end
561
911
  end
562
912
  end
563
913
 
564
- # Return a range of members in a sorted set, by score.
565
- def zrangebyscore(key, min, max, options = {})
566
- command = CommandOptions.new(options) do |c|
567
- c.splat :limit
568
- c.bool :withscores
569
- c.bool :with_scores
570
- end
914
+ # Return a range of members in a sorted set, by index, with scores ordered
915
+ # from high to low.
916
+ #
917
+ # @example Retrieve all members from a sorted set
918
+ # redis.zrevrange("zset", 0, -1)
919
+ # # => ["b", "a"]
920
+ # @example Retrieve all members and their scores from a sorted set
921
+ # redis.zrevrange("zset", 0, -1, :with_scores => true)
922
+ # # => [["b", 64.0], ["a", 32.0]]
923
+ #
924
+ # @see #zrange
925
+ def zrevrange(key, start, stop, options = {})
926
+ args = []
571
927
 
572
- synchronize do
573
- @client.call [:zrangebyscore, key, min, max, *command.to_a]
574
- end
575
- end
928
+ with_scores = options[:with_scores] || options[:withscores]
929
+ args << "WITHSCORES" if with_scores
576
930
 
577
- # Count the members in a sorted set with scores within the given values.
578
- def zcount(key, start, stop)
579
931
  synchronize do
580
- @client.call [:zcount, key, start, stop]
932
+ @client.call [:zrevrange, key, start, stop, *args] do |reply|
933
+ if with_scores
934
+ if reply
935
+ reply.each_slice(2).map do |member, score|
936
+ [member, Float(score)]
937
+ end
938
+ end
939
+ else
940
+ reply
941
+ end
942
+ end
581
943
  end
582
944
  end
583
945
 
584
- # Return a range of members in a sorted set, by index, with scores ordered
585
- # from high to low.
586
- def zrevrange(key, start, stop, options = {})
587
- command = CommandOptions.new(options) do |c|
588
- c.bool :withscores
589
- c.bool :with_scores
590
- end
946
+ # Return a range of members in a sorted set, by score.
947
+ #
948
+ # @example Retrieve members with score `>= 5` and `< 100`
949
+ # redis.zrangebyscore("zset", "5", "(100")
950
+ # # => ["a", "b"]
951
+ # @example Retrieve the first 2 members with score `>= 0`
952
+ # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
953
+ # # => ["a", "b"]
954
+ # @example Retrieve members and their scores with scores `> 5`
955
+ # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
956
+ # # => [["a", 32.0], ["b", 64.0]]
957
+ #
958
+ # @param [String] key
959
+ # @param [String] min
960
+ # - inclusive minimum score is specified verbatim
961
+ # - exclusive minimum score is specified by prefixing `(`
962
+ # @param [String] max
963
+ # - inclusive maximum score is specified verbatim
964
+ # - exclusive maximum score is specified by prefixing `(`
965
+ # @param [Hash] options
966
+ # - `:with_scores => true`: include scores in output
967
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
968
+ # `count` members
969
+ #
970
+ # @return [Array<String>, Array<(String, Float)>]
971
+ # - when `:with_scores` is not specified, an array of members
972
+ # - when `:with_scores` is specified, an array with `(member, score)` pairs
973
+ def zrangebyscore(key, min, max, options = {})
974
+ args = []
975
+
976
+ with_scores = options[:with_scores] || options[:withscores]
977
+ args.concat ["WITHSCORES"] if with_scores
978
+
979
+ limit = options[:limit]
980
+ args.concat ["LIMIT", *limit] if limit
591
981
 
592
982
  synchronize do
593
- @client.call [:zrevrange, key, start, stop, *command.to_a]
983
+ @client.call [:zrangebyscore, key, min, max, *args] do |reply|
984
+ if with_scores
985
+ if reply
986
+ reply.each_slice(2).map do |member, score|
987
+ [member, Float(score)]
988
+ end
989
+ end
990
+ else
991
+ reply
992
+ end
993
+ end
594
994
  end
595
995
  end
596
996
 
597
997
  # Return a range of members in a sorted set, by score, with scores ordered
598
998
  # from high to low.
999
+ #
1000
+ # @example Retrieve members with score `< 100` and `>= 5`
1001
+ # redis.zrevrangebyscore("zset", "(100", "5")
1002
+ # # => ["b", "a"]
1003
+ # @example Retrieve the first 2 members with score `<= 0`
1004
+ # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
1005
+ # # => ["b", "a"]
1006
+ # @example Retrieve members and their scores with scores `> 5`
1007
+ # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
1008
+ # # => [["b", 64.0], ["a", 32.0]]
1009
+ #
1010
+ # @see #zrangebyscore
599
1011
  def zrevrangebyscore(key, max, min, options = {})
600
- command = CommandOptions.new(options) do |c|
601
- c.splat :limit
602
- c.bool :withscores
603
- c.bool :with_scores
1012
+ args = []
1013
+
1014
+ with_scores = options[:with_scores] || options[:withscores]
1015
+ args.concat ["WITHSCORES"] if with_scores
1016
+
1017
+ limit = options[:limit]
1018
+ args.concat ["LIMIT", *limit] if limit
1019
+
1020
+ synchronize do
1021
+ @client.call [:zrevrangebyscore, key, max, min, *args] do |reply|
1022
+ if with_scores
1023
+ if reply
1024
+ reply.each_slice(2).map do |member, score|
1025
+ [member, Float(score)]
1026
+ end
1027
+ end
1028
+ else
1029
+ reply
1030
+ end
1031
+ end
604
1032
  end
1033
+ end
605
1034
 
1035
+ # Count the members in a sorted set with scores within the given values.
1036
+ #
1037
+ # @example Count members with score `>= 5` and `< 100`
1038
+ # redis.zcount("zset", "5", "(100")
1039
+ # # => 2
1040
+ # @example Count members with scores `> 5`
1041
+ # redis.zcount("zset", "(5", "+inf")
1042
+ # # => 2
1043
+ #
1044
+ # @param [String] key
1045
+ # @param [String] min
1046
+ # - inclusive minimum score is specified verbatim
1047
+ # - exclusive minimum score is specified by prefixing `(`
1048
+ # @param [String] max
1049
+ # - inclusive maximum score is specified verbatim
1050
+ # - exclusive maximum score is specified by prefixing `(`
1051
+ # @return [Fixnum] number of members in within the specified range
1052
+ def zcount(key, start, stop)
606
1053
  synchronize do
607
- @client.call [:zrevrangebyscore, key, max, min, *command.to_a]
1054
+ @client.call [:zcount, key, start, stop]
608
1055
  end
609
1056
  end
610
1057
 
611
1058
  # Remove all members in a sorted set within the given scores.
1059
+ #
1060
+ # @example Remove members with score `>= 5` and `< 100`
1061
+ # redis.zremrangebyscore("zset", "5", "(100")
1062
+ # # => 2
1063
+ # @example Remove members with scores `> 5`
1064
+ # redis.zremrangebyscore("zset", "(5", "+inf")
1065
+ # # => 2
1066
+ #
1067
+ # @param [String] key
1068
+ # @param [String] min
1069
+ # - inclusive minimum score is specified verbatim
1070
+ # - exclusive minimum score is specified by prefixing `(`
1071
+ # @param [String] max
1072
+ # - inclusive maximum score is specified verbatim
1073
+ # - exclusive maximum score is specified by prefixing `(`
1074
+ # @return [Fixnum] number of members that were removed
612
1075
  def zremrangebyscore(key, min, max)
613
1076
  synchronize do
614
1077
  @client.call [:zremrangebyscore, key, min, max]
@@ -616,6 +1079,18 @@ class Redis
616
1079
  end
617
1080
 
618
1081
  # Remove all members in a sorted set within the given indexes.
1082
+ #
1083
+ # @example Remove first 5 members
1084
+ # redis.zremrangebyrank("zset", 0, 4)
1085
+ # # => 5
1086
+ # @example Remove last 5 members
1087
+ # redis.zremrangebyrank("zset", -5, -1)
1088
+ # # => 5
1089
+ #
1090
+ # @param [String] key
1091
+ # @param [Fixnum] start start index
1092
+ # @param [Fixnum] stop stop index
1093
+ # @return [Fixnum] number of members that were removed
619
1094
  def zremrangebyrank(key, start, stop)
620
1095
  synchronize do
621
1096
  @client.call [:zremrangebyrank, key, start, stop]
@@ -623,21 +1098,36 @@ class Redis
623
1098
  end
624
1099
 
625
1100
  # Get the score associated with the given member in a sorted set.
1101
+ #
1102
+ # @example Get the score for member "a"
1103
+ # redis.zscore("zset", "a")
1104
+ # # => 32.0
1105
+ #
1106
+ # @param [String] key
1107
+ # @param [String] member
1108
+ # @return [Float] score of the member
626
1109
  def zscore(key, member)
627
1110
  synchronize do
628
- @client.call [:zscore, key, member]
629
- end
630
- end
631
-
632
- # Remove a member from a sorted set.
633
- def zrem(key, member)
634
- synchronize do
635
- _bool @client.call [:zrem, key, member]
1111
+ @client.call [:zscore, key, member] do |reply|
1112
+ Float(reply) if reply
1113
+ end
636
1114
  end
637
1115
  end
638
1116
 
639
1117
  # Intersect multiple sorted sets and store the resulting sorted set in a new
640
1118
  # key.
1119
+ #
1120
+ # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
1121
+ # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
1122
+ # # => 4
1123
+ #
1124
+ # @param [String] destination destination key
1125
+ # @param [Array<String>] keys source keys
1126
+ # @param [Hash] options
1127
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
1128
+ # sorted sets
1129
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1130
+ # @return [Fixnum] number of elements in the resulting sorted set
641
1131
  def zinterstore(destination, keys, options = {})
642
1132
  command = CommandOptions.new(options) do |c|
643
1133
  c.splat :weights
@@ -650,6 +1140,18 @@ class Redis
650
1140
  end
651
1141
 
652
1142
  # Add multiple sorted sets and store the resulting sorted set in a new key.
1143
+ #
1144
+ # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
1145
+ # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
1146
+ # # => 8
1147
+ #
1148
+ # @param [String] destination destination key
1149
+ # @param [Array<String>] keys source keys
1150
+ # @param [Hash] options
1151
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
1152
+ # sorted sets
1153
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1154
+ # @return [Fixnum] number of elements in the resulting sorted set
653
1155
  def zunionstore(destination, keys, options = {})
654
1156
  command = CommandOptions.new(options) do |c|
655
1157
  c.splat :weights
@@ -662,27 +1164,56 @@ class Redis
662
1164
  end
663
1165
 
664
1166
  # Move a key to another database.
1167
+ #
1168
+ # @example Move a key to another database
1169
+ # redis.set "foo", "bar"
1170
+ # # => "OK"
1171
+ # redis.move "foo", 2
1172
+ # # => true
1173
+ # redis.exists "foo"
1174
+ # # => false
1175
+ # redis.select 2
1176
+ # # => "OK"
1177
+ # redis.exists "foo"
1178
+ # # => true
1179
+ # resis.get "foo"
1180
+ # # => "bar"
1181
+ #
1182
+ # @param [String] key
1183
+ # @param [Fixnum] db
1184
+ # @return [Boolean] whether the key was moved or not
665
1185
  def move(key, db)
666
1186
  synchronize do
667
- _bool @client.call [:move, key, db]
1187
+ @client.call [:move, key, db], &_boolify
668
1188
  end
669
1189
  end
670
1190
 
671
1191
  # Set the value of a key, only if the key does not exist.
1192
+ #
1193
+ # @param [String] key
1194
+ # @param [String] value
1195
+ # @return [Boolean] whether the key was set or not
672
1196
  def setnx(key, value)
673
1197
  synchronize do
674
- _bool @client.call [:setnx, key, value]
1198
+ @client.call [:setnx, key, value], &_boolify
675
1199
  end
676
1200
  end
677
1201
 
678
- # Delete a key.
1202
+ # Delete one or more keys.
1203
+ #
1204
+ # @param [String, Array<String>] keys
1205
+ # @return [Fixnum] number of keys that were removed
679
1206
  def del(*keys)
680
1207
  synchronize do
681
1208
  @client.call [:del, *keys]
682
1209
  end
683
1210
  end
684
1211
 
685
- # Rename a key.
1212
+ # Rename a key. If the new key already exists it is overwritten.
1213
+ #
1214
+ # @param [String] old_name
1215
+ # @param [String] new_name
1216
+ # @return [String] `OK`
686
1217
  def rename(old_name, new_name)
687
1218
  synchronize do
688
1219
  @client.call [:rename, old_name, new_name]
@@ -690,27 +1221,43 @@ class Redis
690
1221
  end
691
1222
 
692
1223
  # Rename a key, only if the new key does not exist.
1224
+ #
1225
+ # @param [String] old_name
1226
+ # @param [String] new_name
1227
+ # @return [Boolean] whether the key was renamed or not
693
1228
  def renamenx(old_name, new_name)
694
1229
  synchronize do
695
- _bool @client.call [:renamenx, old_name, new_name]
1230
+ @client.call [:renamenx, old_name, new_name], &_boolify
696
1231
  end
697
1232
  end
698
1233
 
699
1234
  # Set a key's time to live in seconds.
1235
+ #
1236
+ # @param [String] key
1237
+ # @param [Fixnum] seconds time to live. After this timeout has expired,
1238
+ # the key will automatically be deleted
1239
+ # @return [Boolean] whether the timeout was set or not
700
1240
  def expire(key, seconds)
701
1241
  synchronize do
702
- _bool @client.call [:expire, key, seconds]
1242
+ @client.call [:expire, key, seconds], &_boolify
703
1243
  end
704
1244
  end
705
1245
 
706
1246
  # Remove the expiration from a key.
1247
+ #
1248
+ # @param [String] key
1249
+ # @return [Boolean] whether the timeout was removed or not
707
1250
  def persist(key)
708
1251
  synchronize do
709
- _bool @client.call [:persist, key]
1252
+ @client.call [:persist, key], &_boolify
710
1253
  end
711
1254
  end
712
1255
 
713
1256
  # Get the time to live for a key.
1257
+ #
1258
+ # @param [String] key
1259
+ # @return [Fixnum] remaining time to live in seconds, or -1 if the
1260
+ # key does not exist or does not have a timeout
714
1261
  def ttl(key)
715
1262
  synchronize do
716
1263
  @client.call [:ttl, key]
@@ -718,23 +1265,29 @@ class Redis
718
1265
  end
719
1266
 
720
1267
  # Set the expiration for a key as a UNIX timestamp.
1268
+ #
1269
+ # @param [String] key
1270
+ # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp
1271
+ # (seconds since January 1, 1970). After this timeout has expired,
1272
+ # the key will automatically be deleted
1273
+ # @return [Boolean] whether the timeout was set or not
721
1274
  def expireat(key, unix_time)
722
1275
  synchronize do
723
- _bool @client.call [:expireat, key, unix_time]
1276
+ @client.call [:expireat, key, unix_time], &_boolify
724
1277
  end
725
1278
  end
726
1279
 
727
1280
  # Set the string value of a hash field.
728
1281
  def hset(key, field, value)
729
1282
  synchronize do
730
- _bool @client.call [:hset, key, field, value]
1283
+ @client.call [:hset, key, field, value], &_boolify
731
1284
  end
732
1285
  end
733
1286
 
734
1287
  # Set the value of a hash field, only if the field does not exist.
735
1288
  def hsetnx(key, field, value)
736
1289
  synchronize do
737
- _bool @client.call [:hsetnx, key, field, value]
1290
+ @client.call [:hsetnx, key, field, value], &_boolify
738
1291
  end
739
1292
  end
740
1293
 
@@ -750,19 +1303,23 @@ class Redis
750
1303
  end
751
1304
 
752
1305
  # Get the values of all the given hash fields.
753
- def hmget(key, *fields)
1306
+ def hmget(key, *fields, &blk)
754
1307
  synchronize do
755
- @client.call [:hmget, key, *fields]
1308
+ @client.call [:hmget, key, *fields], &blk
756
1309
  end
757
1310
  end
758
1311
 
759
1312
  def mapped_hmget(key, *fields)
760
- reply = hmget(key, *fields)
761
-
762
- if reply.kind_of?(Array)
763
- Hash[*fields.zip(reply).flatten]
764
- else
765
- reply
1313
+ hmget(key, *fields) do |reply|
1314
+ if reply.kind_of?(Array)
1315
+ hash = Hash.new
1316
+ fields.zip(reply).each do |field, value|
1317
+ hash[field] = value
1318
+ end
1319
+ hash
1320
+ else
1321
+ reply
1322
+ end
766
1323
  end
767
1324
  end
768
1325
 
@@ -797,7 +1354,7 @@ class Redis
797
1354
  # Determine if a hash field exists.
798
1355
  def hexists(key, field)
799
1356
  synchronize do
800
- _bool @client.call [:hexists, key, field]
1357
+ @client.call [:hexists, key, field], &_boolify
801
1358
  end
802
1359
  end
803
1360
 
@@ -827,14 +1384,6 @@ class Redis
827
1384
  end
828
1385
  end
829
1386
 
830
- def [](key)
831
- get(key)
832
- end
833
-
834
- def []=(key,value)
835
- set(key, value)
836
- end
837
-
838
1387
  # Set the string value of a key.
839
1388
  def set(key, value)
840
1389
  synchronize do
@@ -842,6 +1391,8 @@ class Redis
842
1391
  end
843
1392
  end
844
1393
 
1394
+ alias :[]= :set
1395
+
845
1396
  # Sets or clears the bit at offset in the string value stored at key.
846
1397
  def setbit(key, offset, value)
847
1398
  synchronize do
@@ -886,12 +1437,16 @@ class Redis
886
1437
  end
887
1438
 
888
1439
  def mapped_mget(*keys)
889
- reply = mget(*keys)
890
-
891
- if reply.kind_of?(Array)
892
- Hash[*keys.zip(reply).flatten]
893
- else
894
- reply
1440
+ mget(*keys) do |reply|
1441
+ if reply.kind_of?(Array)
1442
+ hash = Hash.new
1443
+ keys.zip(reply).each do |field, value|
1444
+ hash[field] = value
1445
+ end
1446
+ hash
1447
+ else
1448
+ reply
1449
+ end
895
1450
  end
896
1451
  end
897
1452
 
@@ -939,6 +1494,9 @@ class Redis
939
1494
  end
940
1495
 
941
1496
  # Determine the type stored at key.
1497
+ #
1498
+ # @param [String] key
1499
+ # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
942
1500
  def type(key)
943
1501
  synchronize do
944
1502
  @client.call [:type, key]
@@ -950,7 +1508,7 @@ class Redis
950
1508
  synchronize do
951
1509
  begin
952
1510
  @client.call [:quit]
953
- rescue Errno::ECONNRESET
1511
+ rescue ConnectionError
954
1512
  ensure
955
1513
  @client.disconnect
956
1514
  end
@@ -960,7 +1518,14 @@ class Redis
960
1518
  # Synchronously save the dataset to disk and then shut down the server.
961
1519
  def shutdown
962
1520
  synchronize do
963
- @client.call_without_reply [:shutdown]
1521
+ @client.without_reconnect do
1522
+ begin
1523
+ @client.call [:shutdown]
1524
+ rescue ConnectionError
1525
+ # This means Redis has probably exited.
1526
+ nil
1527
+ end
1528
+ end
964
1529
  end
965
1530
  end
966
1531
 
@@ -971,12 +1536,12 @@ class Redis
971
1536
  end
972
1537
  end
973
1538
 
974
- def pipelined(options = {})
1539
+ def pipelined
975
1540
  synchronize do
976
1541
  begin
977
1542
  original, @client = @client, Pipeline.new
978
1543
  yield
979
- original.call_pipelined(@client.commands, options) unless @client.commands.empty?
1544
+ original.call_pipeline(@client)
980
1545
  ensure
981
1546
  @client = original
982
1547
  end
@@ -1008,15 +1573,16 @@ class Redis
1008
1573
  def multi
1009
1574
  synchronize do
1010
1575
  if !block_given?
1011
- @client.call :multi
1576
+ @client.call [:multi]
1012
1577
  else
1013
- result = pipelined(:raise => false) do
1014
- multi
1578
+ begin
1579
+ pipeline = Pipeline::Multi.new
1580
+ original, @client = @client, pipeline
1015
1581
  yield(self)
1016
- exec
1582
+ original.call_pipeline(pipeline)
1583
+ ensure
1584
+ @client = original
1017
1585
  end
1018
-
1019
- result.last
1020
1586
  end
1021
1587
  end
1022
1588
  end
@@ -1123,8 +1689,10 @@ private
1123
1689
  # Commands returning 1 for true and 0 for false may be executed in a pipeline
1124
1690
  # where the method call will return nil. Propagate the nil instead of falsely
1125
1691
  # returning false.
1126
- def _bool(value)
1127
- value == 1 if value
1692
+ def _boolify
1693
+ lambda { |value|
1694
+ value == 1 if value
1695
+ }
1128
1696
  end
1129
1697
 
1130
1698
  def subscription(method, channels, block)
@@ -1145,4 +1713,3 @@ require "redis/connection"
1145
1713
  require "redis/client"
1146
1714
  require "redis/pipeline"
1147
1715
  require "redis/subscribe"
1148
- require "redis/compat"