redis 2.2.2 → 3.0.0.rc1

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