gorsuch-redis 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 (87) hide show
  1. data/.gitignore +10 -0
  2. data/.yardopts +3 -0
  3. data/CHANGELOG.md +113 -0
  4. data/LICENSE +20 -0
  5. data/README.md +214 -0
  6. data/Rakefile +260 -0
  7. data/TODO.md +4 -0
  8. data/benchmarking/logging.rb +62 -0
  9. data/benchmarking/pipeline.rb +51 -0
  10. data/benchmarking/speed.rb +21 -0
  11. data/benchmarking/suite.rb +24 -0
  12. data/benchmarking/thread_safety.rb +38 -0
  13. data/benchmarking/worker.rb +71 -0
  14. data/examples/basic.rb +15 -0
  15. data/examples/dist_redis.rb +43 -0
  16. data/examples/incr-decr.rb +17 -0
  17. data/examples/list.rb +26 -0
  18. data/examples/pubsub.rb +31 -0
  19. data/examples/sets.rb +36 -0
  20. data/examples/unicorn/config.ru +3 -0
  21. data/examples/unicorn/unicorn.rb +20 -0
  22. data/lib/redis/client.rb +303 -0
  23. data/lib/redis/connection/command_helper.rb +44 -0
  24. data/lib/redis/connection/hiredis.rb +52 -0
  25. data/lib/redis/connection/registry.rb +12 -0
  26. data/lib/redis/connection/ruby.rb +136 -0
  27. data/lib/redis/connection/synchrony.rb +131 -0
  28. data/lib/redis/connection.rb +9 -0
  29. data/lib/redis/distributed.rb +696 -0
  30. data/lib/redis/errors.rb +38 -0
  31. data/lib/redis/hash_ring.rb +131 -0
  32. data/lib/redis/pipeline.rb +106 -0
  33. data/lib/redis/subscribe.rb +79 -0
  34. data/lib/redis/version.rb +3 -0
  35. data/lib/redis.rb +1724 -0
  36. data/redis.gemspec +43 -0
  37. data/test/command_map_test.rb +29 -0
  38. data/test/commands_on_hashes_test.rb +20 -0
  39. data/test/commands_on_lists_test.rb +60 -0
  40. data/test/commands_on_sets_test.rb +76 -0
  41. data/test/commands_on_sorted_sets_test.rb +108 -0
  42. data/test/commands_on_strings_test.rb +80 -0
  43. data/test/commands_on_value_types_test.rb +87 -0
  44. data/test/connection_handling_test.rb +204 -0
  45. data/test/db/.gitignore +1 -0
  46. data/test/distributed_blocking_commands_test.rb +53 -0
  47. data/test/distributed_commands_on_hashes_test.rb +11 -0
  48. data/test/distributed_commands_on_lists_test.rb +23 -0
  49. data/test/distributed_commands_on_sets_test.rb +84 -0
  50. data/test/distributed_commands_on_sorted_sets_test.rb +19 -0
  51. data/test/distributed_commands_on_strings_test.rb +49 -0
  52. data/test/distributed_commands_on_value_types_test.rb +72 -0
  53. data/test/distributed_commands_requiring_clustering_test.rb +148 -0
  54. data/test/distributed_connection_handling_test.rb +24 -0
  55. data/test/distributed_internals_test.rb +27 -0
  56. data/test/distributed_key_tags_test.rb +52 -0
  57. data/test/distributed_persistence_control_commands_test.rb +23 -0
  58. data/test/distributed_publish_subscribe_test.rb +100 -0
  59. data/test/distributed_remote_server_control_commands_test.rb +42 -0
  60. data/test/distributed_sorting_test.rb +21 -0
  61. data/test/distributed_test.rb +59 -0
  62. data/test/distributed_transactions_test.rb +33 -0
  63. data/test/encoding_test.rb +15 -0
  64. data/test/error_replies_test.rb +53 -0
  65. data/test/helper.rb +155 -0
  66. data/test/helper_test.rb +8 -0
  67. data/test/internals_test.rb +152 -0
  68. data/test/lint/hashes.rb +140 -0
  69. data/test/lint/internals.rb +36 -0
  70. data/test/lint/lists.rb +107 -0
  71. data/test/lint/sets.rb +90 -0
  72. data/test/lint/sorted_sets.rb +196 -0
  73. data/test/lint/strings.rb +133 -0
  74. data/test/lint/value_types.rb +81 -0
  75. data/test/persistence_control_commands_test.rb +21 -0
  76. data/test/pipelining_commands_test.rb +186 -0
  77. data/test/publish_subscribe_test.rb +158 -0
  78. data/test/redis_mock.rb +89 -0
  79. data/test/remote_server_control_commands_test.rb +88 -0
  80. data/test/sorting_test.rb +43 -0
  81. data/test/synchrony_driver.rb +57 -0
  82. data/test/test.conf +9 -0
  83. data/test/thread_safety_test.rb +30 -0
  84. data/test/transactions_test.rb +173 -0
  85. data/test/unknown_commands_test.rb +13 -0
  86. data/test/url_param_test.rb +59 -0
  87. metadata +236 -0
@@ -0,0 +1,38 @@
1
+ class Redis
2
+ # Base error for all redis-rb errors.
3
+ class BaseError < RuntimeError
4
+ end
5
+
6
+ # Raised by the connection when a protocol error occurs.
7
+ class ProtocolError < BaseError
8
+ def initialize(reply_type)
9
+ super(<<-EOS.gsub(/(?:^|\n)\s*/, " "))
10
+ Got '#{reply_type}' as initial reply byte.
11
+ If you're running in a multi-threaded environment, make sure you
12
+ pass the :thread_safe option when initializing the connection.
13
+ If you're in a forking environment, such as Unicorn, you need to
14
+ connect to Redis after forking.
15
+ EOS
16
+ end
17
+ end
18
+
19
+ # Raised by the client when command execution returns an error reply.
20
+ class CommandError < BaseError
21
+ end
22
+
23
+ # Base error for connection related errors.
24
+ class BaseConnectionError < BaseError
25
+ end
26
+
27
+ # Raised when connection to a Redis server cannot be made.
28
+ class CannotConnectError < BaseConnectionError
29
+ end
30
+
31
+ # Raised when connection to a Redis server is lost.
32
+ class ConnectionError < BaseConnectionError
33
+ end
34
+
35
+ # Raised when performing I/O times out.
36
+ class TimeoutError < BaseConnectionError
37
+ end
38
+ end
@@ -0,0 +1,131 @@
1
+ require 'zlib'
2
+
3
+ class Redis
4
+ class HashRing
5
+
6
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
7
+
8
+ attr_reader :ring, :sorted_keys, :replicas, :nodes
9
+
10
+ # nodes is a list of objects that have a proper to_s representation.
11
+ # replicas indicates how many virtual points should be used pr. node,
12
+ # replicas are required to improve the distribution.
13
+ def initialize(nodes=[], replicas=POINTS_PER_SERVER)
14
+ @replicas = replicas
15
+ @ring = {}
16
+ @nodes = []
17
+ @sorted_keys = []
18
+ nodes.each do |node|
19
+ add_node(node)
20
+ end
21
+ end
22
+
23
+ # Adds a `node` to the hash ring (including a number of replicas).
24
+ def add_node(node)
25
+ @nodes << node
26
+ @replicas.times do |i|
27
+ key = Zlib.crc32("#{node.id}:#{i}")
28
+ @ring[key] = node
29
+ @sorted_keys << key
30
+ end
31
+ @sorted_keys.sort!
32
+ end
33
+
34
+ def remove_node(node)
35
+ @nodes.reject!{|n| n.id == node.id}
36
+ @replicas.times do |i|
37
+ key = Zlib.crc32("#{node.id}:#{i}")
38
+ @ring.delete(key)
39
+ @sorted_keys.reject! {|k| k == key}
40
+ end
41
+ end
42
+
43
+ # get the node in the hash ring for this key
44
+ def get_node(key)
45
+ get_node_pos(key)[0]
46
+ end
47
+
48
+ def get_node_pos(key)
49
+ return [nil,nil] if @ring.size == 0
50
+ crc = Zlib.crc32(key)
51
+ idx = HashRing.binary_search(@sorted_keys, crc)
52
+ return [@ring[@sorted_keys[idx]], idx]
53
+ end
54
+
55
+ def iter_nodes(key)
56
+ return [nil,nil] if @ring.size == 0
57
+ _, pos = get_node_pos(key)
58
+ @sorted_keys[pos..-1].each do |k|
59
+ yield @ring[k]
60
+ end
61
+ end
62
+
63
+ class << self
64
+
65
+ # gem install RubyInline to use this code
66
+ # Native extension to perform the binary search within the hashring.
67
+ # There's a pure ruby version below so this is purely optional
68
+ # for performance. In testing 20k gets and sets, the native
69
+ # binary search shaved about 12% off the runtime (9sec -> 8sec).
70
+ begin
71
+ require 'inline'
72
+ inline do |builder|
73
+ builder.c <<-EOM
74
+ int binary_search(VALUE ary, unsigned int r) {
75
+ int upper = RARRAY_LEN(ary) - 1;
76
+ int lower = 0;
77
+ int idx = 0;
78
+
79
+ while (lower <= upper) {
80
+ idx = (lower + upper) / 2;
81
+
82
+ VALUE continuumValue = RARRAY_PTR(ary)[idx];
83
+ unsigned int l = NUM2UINT(continuumValue);
84
+ if (l == r) {
85
+ return idx;
86
+ }
87
+ else if (l > r) {
88
+ upper = idx - 1;
89
+ }
90
+ else {
91
+ lower = idx + 1;
92
+ }
93
+ }
94
+ if (upper < 0) {
95
+ upper = RARRAY_LEN(ary) - 1;
96
+ }
97
+ return upper;
98
+ }
99
+ EOM
100
+ end
101
+ rescue Exception
102
+ # Find the closest index in HashRing with value <= the given value
103
+ def binary_search(ary, value, &block)
104
+ upper = ary.size - 1
105
+ lower = 0
106
+ idx = 0
107
+
108
+ while(lower <= upper) do
109
+ idx = (lower + upper) / 2
110
+ comp = ary[idx] <=> value
111
+
112
+ if comp == 0
113
+ return idx
114
+ elsif comp > 0
115
+ upper = idx - 1
116
+ else
117
+ lower = idx + 1
118
+ end
119
+ end
120
+
121
+ if upper < 0
122
+ upper = ary.size - 1
123
+ end
124
+ return upper
125
+ end
126
+
127
+ end
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,106 @@
1
+ class Redis
2
+ unless defined?(::BasicObject)
3
+ class BasicObject
4
+ instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
5
+ end
6
+ end
7
+
8
+ class Pipeline
9
+ attr :futures
10
+
11
+ def initialize
12
+ @without_reconnect = false
13
+ @shutdown = false
14
+ @futures = []
15
+ end
16
+
17
+ def without_reconnect?
18
+ @without_reconnect
19
+ end
20
+
21
+ def shutdown?
22
+ @shutdown
23
+ end
24
+
25
+ def call(command, &block)
26
+ # A pipeline that contains a shutdown should not raise ECONNRESET when
27
+ # the connection is gone.
28
+ @shutdown = true if command.first == :shutdown
29
+ future = Future.new(command, block)
30
+ @futures << future
31
+ future
32
+ end
33
+
34
+ def call_pipeline(pipeline)
35
+ @shutdown = true if pipeline.shutdown?
36
+ @futures.concat(pipeline.futures)
37
+ nil
38
+ end
39
+
40
+ def commands
41
+ @futures.map { |f| f._command }
42
+ end
43
+
44
+ def without_reconnect(&block)
45
+ @without_reconnect = true
46
+ yield
47
+ end
48
+
49
+ def finish(replies)
50
+ futures.each_with_index.map do |future, i|
51
+ future._set(replies[i])
52
+ end
53
+ end
54
+
55
+ class Multi < self
56
+ def finish(replies)
57
+ return if replies.last.nil? # The transaction failed because of WATCH.
58
+
59
+ if replies.last.size < futures.size - 2
60
+ # Some command wasn't recognized by Redis.
61
+ raise replies.detect { |r| r.kind_of?(::Exception) }
62
+ end
63
+
64
+ super(replies.last)
65
+ end
66
+
67
+ def commands
68
+ [[:multi]] + super + [[:exec]]
69
+ end
70
+ end
71
+ end
72
+
73
+ class FutureNotReady < RuntimeError
74
+ def initialize
75
+ super("Value will be available once the pipeline executes.")
76
+ end
77
+ end
78
+
79
+ class Future < BasicObject
80
+ FutureNotReady = ::Redis::FutureNotReady.new
81
+
82
+ def initialize(command, transformation)
83
+ @command = command
84
+ @transformation = transformation
85
+ @object = FutureNotReady
86
+ end
87
+
88
+ def inspect
89
+ "<Redis::Future #{@command.inspect}>"
90
+ end
91
+
92
+ def _set(object)
93
+ @object = @transformation ? @transformation.call(object) : object
94
+ value
95
+ end
96
+
97
+ def _command
98
+ @command
99
+ end
100
+
101
+ def value
102
+ ::Kernel.raise(@object) if @object.kind_of?(::Exception)
103
+ @object
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,79 @@
1
+ class Redis
2
+ class SubscribedClient
3
+ def initialize(client)
4
+ @client = client
5
+ end
6
+
7
+ def call(command)
8
+ @client.process([command])
9
+ end
10
+
11
+ def subscribe(*channels, &block)
12
+ subscription("subscribe", "unsubscribe", channels, block)
13
+ end
14
+
15
+ def psubscribe(*channels, &block)
16
+ subscription("psubscribe", "punsubscribe", channels, block)
17
+ end
18
+
19
+ def unsubscribe(*channels)
20
+ call [:unsubscribe, *channels]
21
+ end
22
+
23
+ def punsubscribe(*channels)
24
+ call [:punsubscribe, *channels]
25
+ end
26
+
27
+ protected
28
+
29
+ def subscription(start, stop, channels, block)
30
+ sub = Subscription.new(&block)
31
+
32
+ begin
33
+ @client.call_loop([start, *channels]) do |line|
34
+ type, *rest = line
35
+ sub.callbacks[type].call(*rest)
36
+ break if type == stop && rest.last == 0
37
+ end
38
+ ensure
39
+ send(stop)
40
+ end
41
+ end
42
+ end
43
+
44
+ class Subscription
45
+ attr :callbacks
46
+
47
+ def initialize
48
+ @callbacks = Hash.new do |hash, key|
49
+ hash[key] = lambda { |*_| }
50
+ end
51
+
52
+ yield(self)
53
+ end
54
+
55
+ def subscribe(&block)
56
+ @callbacks["subscribe"] = block
57
+ end
58
+
59
+ def unsubscribe(&block)
60
+ @callbacks["unsubscribe"] = block
61
+ end
62
+
63
+ def message(&block)
64
+ @callbacks["message"] = block
65
+ end
66
+
67
+ def psubscribe(&block)
68
+ @callbacks["psubscribe"] = block
69
+ end
70
+
71
+ def punsubscribe(&block)
72
+ @callbacks["punsubscribe"] = block
73
+ end
74
+
75
+ def pmessage(&block)
76
+ @callbacks["pmessage"] = block
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ class Redis
2
+ VERSION = "3.0.0.rc1"
3
+ end
data/lib/redis.rb ADDED
@@ -0,0 +1,1724 @@
1
+ require "monitor"
2
+ require "redis/errors"
3
+ require "uri"
4
+
5
+ class Redis
6
+
7
+ def self.deprecate(message, trace = caller[0])
8
+ $stderr.puts "\n#{message} (in #{trace})"
9
+ end
10
+
11
+ attr :client
12
+
13
+ def self.connect(options = {})
14
+ new(options)
15
+ end
16
+
17
+ def self.current
18
+ Thread.current[:redis] ||= Redis.connect
19
+ end
20
+
21
+ def self.current=(redis)
22
+ Thread.current[:redis] = redis
23
+ end
24
+
25
+ include MonitorMixin
26
+
27
+ def parse_options(options)
28
+ options = options.dup
29
+
30
+ if options[:path]
31
+ uri = URI::Generic.new('unix', nil, nil, nil, nil, options[:path], nil, nil, nil)
32
+ else
33
+ url = options.delete(:url) || ENV["REDIS_URL"]
34
+ if url
35
+ uri = URI.parse(url)
36
+ else
37
+ uri = URI::Generic.new('redis', nil, '127.0.0.1', 6379, nil, '/0', nil, nil, nil)
38
+ end
39
+
40
+ uri.host = options.delete(:host) if options[:host]
41
+ uri.port = options.delete(:port) if options[:port]
42
+ uri.user = 'redis'
43
+ uri.password = options.delete(:password) if options[:password]
44
+ uri.path = "/#{options.delete(:db)}" if options[:db]
45
+ end
46
+
47
+ options[:uri] = uri
48
+ options
49
+ end
50
+
51
+ def initialize(options = {})
52
+ options = parse_options(options)
53
+ @client = Client.new(options)
54
+
55
+ if options[:thread_safe] == false
56
+ @synchronizer = lambda { |&block| block.call }
57
+ else
58
+ @synchronizer = lambda { |&block| mon_synchronize { block.call } }
59
+ super() # Monitor#initialize
60
+ end
61
+ end
62
+
63
+ def synchronize
64
+ @synchronizer.call { yield }
65
+ end
66
+
67
+ # Run code without the client reconnecting
68
+ def without_reconnect(&block)
69
+ synchronize do
70
+ @client.without_reconnect(&block)
71
+ end
72
+ end
73
+
74
+ # Authenticate to the server.
75
+ #
76
+ # @param [String] password must match the password specified in the
77
+ # `requirepass` directive in the configuration file
78
+ # @return [String] `OK`
79
+ def auth(password)
80
+ synchronize do
81
+ @client.call [:auth, password]
82
+ end
83
+ end
84
+
85
+ # Change the selected database for the current connection.
86
+ #
87
+ # @param [Fixnum] db zero-based index of the DB to use (0 to 15)
88
+ # @return [String] `OK`
89
+ def select(db)
90
+ synchronize do
91
+ @client.db = db
92
+ @client.call [:select, db]
93
+ end
94
+ end
95
+
96
+ # Get information and statistics about the server.
97
+ #
98
+ # @param [String, Symbol] cmd e.g. "commandstats"
99
+ # @return [Hash<String, String>]
100
+ def info(cmd = nil)
101
+ synchronize do
102
+ @client.call [:info, cmd].compact do |reply|
103
+ if reply.kind_of?(String)
104
+ reply = Hash[*reply.split(/:|\r\n/).grep(/^[^#]/)]
105
+
106
+ if cmd && cmd.to_s == "commandstats"
107
+ # Extract nested hashes for INFO COMMANDSTATS
108
+ reply = Hash[reply.map do |k, v|
109
+ [k[/^cmdstat_(.*)$/, 1], Hash[*v.split(/,|=/)]]
110
+ end]
111
+ end
112
+ end
113
+
114
+ reply
115
+ end
116
+ end
117
+ end
118
+
119
+ # Get or set server configuration parameters.
120
+ #
121
+ # @param [String] action e.g. `get`, `set`, `resetstat`
122
+ # @return [String, Hash] string reply, or hash when retrieving more than one
123
+ # property with `CONFIG GET`
124
+ def config(action, *args)
125
+ synchronize do
126
+ @client.call [:config, action, *args] do |reply|
127
+ if reply.kind_of?(Array) && action == :get
128
+ Hash[*reply]
129
+ else
130
+ reply
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ # Remove all keys from the current database.
137
+ #
138
+ # @return [String] `OK`
139
+ def flushdb
140
+ synchronize do
141
+ @client.call [:flushdb]
142
+ end
143
+ end
144
+
145
+ # Remove all keys from all databases.
146
+ #
147
+ # @return [String] `OK`
148
+ def flushall
149
+ synchronize do
150
+ @client.call [:flushall]
151
+ end
152
+ end
153
+
154
+ # Synchronously save the dataset to disk.
155
+ #
156
+ # @return [String]
157
+ def save
158
+ synchronize do
159
+ @client.call [:save]
160
+ end
161
+ end
162
+
163
+ # Asynchronously save the dataset to disk.
164
+ #
165
+ # @return [String] `OK`
166
+ def bgsave
167
+ synchronize do
168
+ @client.call [:bgsave]
169
+ end
170
+ end
171
+
172
+ # Asynchronously rewrite the append-only file.
173
+ #
174
+ # @return [String] `OK`
175
+ def bgrewriteaof
176
+ synchronize do
177
+ @client.call [:bgrewriteaof]
178
+ end
179
+ end
180
+
181
+ # Get the value of a key.
182
+ #
183
+ # @param [String] key
184
+ # @return [String]
185
+ def get(key)
186
+ synchronize do
187
+ @client.call [:get, key]
188
+ end
189
+ end
190
+
191
+ alias :[] :get
192
+
193
+ # Returns the bit value at offset in the string value stored at key.
194
+ #
195
+ # @param [String] key
196
+ # @param [Fixnum] offset bit offset
197
+ # @return [Fixnum] `0` or `1`
198
+ def getbit(key, offset)
199
+ synchronize do
200
+ @client.call [:getbit, key, offset]
201
+ end
202
+ end
203
+
204
+ # Get a substring of the string stored at a key.
205
+ #
206
+ # @param [String] key
207
+ # @param [Fixnum] start zero-based start offset
208
+ # @param [Fixnum] stop zero-based end offset. Use -1 for representing
209
+ # the end of the string
210
+ # @return [Fixnum] `0` or `1`
211
+ def getrange(key, start, stop)
212
+ synchronize do
213
+ @client.call [:getrange, key, start, stop]
214
+ end
215
+ end
216
+
217
+ # Set the string value of a key and return its old value.
218
+ #
219
+ # @param [String] key
220
+ # @param [String] value value to replace the current value with
221
+ # @return [String] the old value stored in the key, or `nil` if the key
222
+ # did not exist
223
+ def getset(key, value)
224
+ synchronize do
225
+ @client.call [:getset, key, value]
226
+ end
227
+ end
228
+
229
+ # Get the values of all the given keys.
230
+ #
231
+ # @param [Array<String>] keys
232
+ # @return [Array<String>]
233
+ def mget(*keys, &blk)
234
+ synchronize do
235
+ @client.call [:mget, *keys], &blk
236
+ end
237
+ end
238
+
239
+ # Append a value to a key.
240
+ #
241
+ # @param [String] key
242
+ # @param [String] value value to append
243
+ # @return [Fixnum] length of the string after appending
244
+ def append(key, value)
245
+ synchronize do
246
+ @client.call [:append, key, value]
247
+ end
248
+ end
249
+
250
+ # Get the length of the value stored in a key.
251
+ #
252
+ # @param [String] key
253
+ # @return [Fixnum] the length of the value stored in the key, or 0
254
+ # if the key does not exist
255
+ def strlen(key)
256
+ synchronize do
257
+ @client.call [:strlen, key]
258
+ end
259
+ end
260
+
261
+ # Get all the fields and values in a hash.
262
+ #
263
+ # @param [String] key
264
+ # @return [Hash<String, String>]
265
+ def hgetall(key)
266
+ synchronize do
267
+ @client.call [:hgetall, key] do |reply|
268
+ if reply.kind_of?(Array)
269
+ hash = Hash.new
270
+ reply.each_slice(2) do |field, value|
271
+ hash[field] = value
272
+ end
273
+ hash
274
+ else
275
+ reply
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ # Get the value of a hash field.
282
+ #
283
+ # @param [String] key
284
+ # @param [String] field
285
+ # @return [String]
286
+ def hget(key, field)
287
+ synchronize do
288
+ @client.call [:hget, key, field]
289
+ end
290
+ end
291
+
292
+ # Delete one or more hash fields.
293
+ #
294
+ # @param [String] key
295
+ # @param [String, Array<String>] field
296
+ # @return [Fixnum] the number of fields that were removed from the hash
297
+ def hdel(key, field)
298
+ synchronize do
299
+ @client.call [:hdel, key, field]
300
+ end
301
+ end
302
+
303
+ # Get all the fields in a hash.
304
+ #
305
+ # @param [String] key
306
+ # @return [Array<String>]
307
+ def hkeys(key)
308
+ synchronize do
309
+ @client.call [:hkeys, key]
310
+ end
311
+ end
312
+
313
+ # Find all keys matching the given pattern.
314
+ #
315
+ # @param [String] pattern
316
+ # @return [Array<String>]
317
+ def keys(pattern = "*")
318
+ synchronize do
319
+ @client.call [:keys, pattern] do |reply|
320
+ if reply.kind_of?(String)
321
+ reply.split(" ")
322
+ else
323
+ reply
324
+ end
325
+ end
326
+ end
327
+ end
328
+
329
+ # Return a random key from the keyspace.
330
+ #
331
+ # @return [String]
332
+ def randomkey
333
+ synchronize do
334
+ @client.call [:randomkey]
335
+ end
336
+ end
337
+
338
+ # Echo the given string.
339
+ #
340
+ # @param [String] value
341
+ # @return [String]
342
+ def echo(value)
343
+ synchronize do
344
+ @client.call [:echo, value]
345
+ end
346
+ end
347
+
348
+ # Ping the server.
349
+ #
350
+ # @return [String] `PONG`
351
+ def ping
352
+ synchronize do
353
+ @client.call [:ping]
354
+ end
355
+ end
356
+
357
+ # Get the UNIX time stamp of the last successful save to disk.
358
+ #
359
+ # @return [Fixnum]
360
+ def lastsave
361
+ synchronize do
362
+ @client.call [:lastsave]
363
+ end
364
+ end
365
+
366
+ # Return the number of keys in the selected database.
367
+ #
368
+ # @return [Fixnum]
369
+ def dbsize
370
+ synchronize do
371
+ @client.call [:dbsize]
372
+ end
373
+ end
374
+
375
+ # Determine if a key exists.
376
+ #
377
+ # @param [String] key
378
+ # @return [Boolean]
379
+ def exists(key)
380
+ synchronize do
381
+ @client.call [:exists, key], &_boolify
382
+ end
383
+ end
384
+
385
+ # Get the length of a list.
386
+ #
387
+ # @param [String] key
388
+ # @return [Fixnum]
389
+ def llen(key)
390
+ synchronize do
391
+ @client.call [:llen, key]
392
+ end
393
+ end
394
+
395
+ # Get a range of elements from a list.
396
+ #
397
+ # @param [String] key
398
+ # @param [Fixnum] start start index
399
+ # @param [Fixnum] stop stop index
400
+ # @return [Array<String>]
401
+ def lrange(key, start, stop)
402
+ synchronize do
403
+ @client.call [:lrange, key, start, stop]
404
+ end
405
+ end
406
+
407
+ # Trim a list to the specified range.
408
+ #
409
+ # @param [String] key
410
+ # @param [Fixnum] start start index
411
+ # @param [Fixnum] stop stop index
412
+ # @return [String] `OK`
413
+ def ltrim(key, start, stop)
414
+ synchronize do
415
+ @client.call [:ltrim, key, start, stop]
416
+ end
417
+ end
418
+
419
+ # Get an element from a list by its index.
420
+ #
421
+ # @param [String] key
422
+ # @param [Fixnum] index
423
+ # @return [String]
424
+ def lindex(key, index)
425
+ synchronize do
426
+ @client.call [:lindex, key, index]
427
+ end
428
+ end
429
+
430
+ # Insert an element before or after another element in a list.
431
+ #
432
+ # @param [String] key
433
+ # @param [String, Symbol] where `BEFORE` or `AFTER`
434
+ # @param [String] pivot reference element
435
+ # @param [String] value
436
+ # @return [Fixnum] length of the list after the insert operation, or `-1`
437
+ # when the element `pivot` was not found
438
+ def linsert(key, where, pivot, value)
439
+ synchronize do
440
+ @client.call [:linsert, key, where, pivot, value]
441
+ end
442
+ end
443
+
444
+ # Set the value of an element in a list by its index.
445
+ #
446
+ # @param [String] key
447
+ # @param [Fixnum] index
448
+ # @param [String] value
449
+ # @return [String] `OK`
450
+ def lset(key, index, value)
451
+ synchronize do
452
+ @client.call [:lset, key, index, value]
453
+ end
454
+ end
455
+
456
+ # Remove elements from a list.
457
+ #
458
+ # @param [String] key
459
+ # @param [Fixnum] count number of elements to remove. Use a positive
460
+ # value to remove the first `count` occurrences of `value`. A negative
461
+ # value to remove the last `count` occurrences of `value`. Or zero, to
462
+ # remove all occurrences of `value` from the list.
463
+ # @param [String] value
464
+ # @return [Fixnum] the number of removed elements
465
+ def lrem(key, count, value)
466
+ synchronize do
467
+ @client.call [:lrem, key, count, value]
468
+ end
469
+ end
470
+
471
+ # Append one or more values to a list, creating the list if it doesn't exist
472
+ #
473
+ # @param [String] key
474
+ # @param [String] value
475
+ # @return [Fixnum] the length of the list after the push operation
476
+ def rpush(key, value)
477
+ synchronize do
478
+ @client.call [:rpush, key, value]
479
+ end
480
+ end
481
+
482
+ # Append a value to a list, only if the list exists.
483
+ #
484
+ # @param [String] key
485
+ # @param [String] value
486
+ # @return [Fixnum] the length of the list after the push operation
487
+ def rpushx(key, value)
488
+ synchronize do
489
+ @client.call [:rpushx, key, value]
490
+ end
491
+ end
492
+
493
+ # Prepend one or more values to a list, creating the list if it doesn't exist
494
+ #
495
+ # @param [String] key
496
+ # @param [String] value
497
+ # @return [Fixnum] the length of the list after the push operation
498
+ def lpush(key, value)
499
+ synchronize do
500
+ @client.call [:lpush, key, value]
501
+ end
502
+ end
503
+
504
+ # Prepend a value to a list, only if the list exists.
505
+ #
506
+ # @param [String] key
507
+ # @param [String] value
508
+ # @return [Fixnum] the length of the list after the push operation
509
+ def lpushx(key, value)
510
+ synchronize do
511
+ @client.call [:lpushx, key, value]
512
+ end
513
+ end
514
+
515
+ # Remove and get the last element in a list.
516
+ #
517
+ # @param [String] key
518
+ # @return [String]
519
+ def rpop(key)
520
+ synchronize do
521
+ @client.call [:rpop, key]
522
+ end
523
+ end
524
+
525
+ # Remove and get the first element in a list, or block until one is available.
526
+ #
527
+ # @param [Array<String>] args one or more keys to perform a blocking pop on,
528
+ # followed by a `Fixnum` timeout value
529
+ # @return [nil, Array<String>] tuple of list that was popped from and element
530
+ # that was popped, or nil when the blocking operation timed out
531
+ def blpop(*args)
532
+ synchronize do
533
+ @client.call_without_timeout [:blpop, *args]
534
+ end
535
+ end
536
+
537
+ # Remove and get the last element in a list, or block until one is available.
538
+ #
539
+ # @param [Array<String>] args one or more keys to perform a blocking pop on,
540
+ # followed by a `Fixnum` timeout value
541
+ # @return [nil, Array<String>] tuple of list that was popped from and element
542
+ # that was popped, or nil when the blocking operation timed out
543
+ def brpop(*args)
544
+ synchronize do
545
+ @client.call_without_timeout [:brpop, *args]
546
+ end
547
+ end
548
+
549
+ # Pop a value from a list, push it to another list and return it; or block
550
+ # until one is available.
551
+ #
552
+ # @param [String] source source key
553
+ # @param [String] destination destination key
554
+ # @param [Fixnum] timeout
555
+ # @return [nil, String] the element, or nil when the blocking operation timed out
556
+ def brpoplpush(source, destination, timeout)
557
+ synchronize do
558
+ @client.call_without_timeout [:brpoplpush, source, destination, timeout]
559
+ end
560
+ end
561
+
562
+ # Remove the last element in a list, append it to another list and return it.
563
+ #
564
+ # @param [String] source source key
565
+ # @param [String] destination destination key
566
+ # @return [nil, String] the element, or nil when the source key does not exist
567
+ def rpoplpush(source, destination)
568
+ synchronize do
569
+ @client.call [:rpoplpush, source, destination]
570
+ end
571
+ end
572
+
573
+ # Remove and get the first element in a list.
574
+ #
575
+ # @param [String] key
576
+ # @return [String]
577
+ def lpop(key)
578
+ synchronize do
579
+ @client.call [:lpop, key]
580
+ end
581
+ end
582
+
583
+ # Interact with the slowlog (get, len, reset)
584
+ #
585
+ # @param [String] subcommand e.g. `get`, `len`, `reset`
586
+ # @param [Fixnum] length maximum number of entries to return
587
+ # @return [Array<String>, Fixnum, String] depends on subcommand
588
+ def slowlog(subcommand, length=nil)
589
+ synchronize do
590
+ args = [:slowlog, subcommand]
591
+ args << length if length
592
+ @client.call args
593
+ end
594
+ end
595
+
596
+ # Get all the members in a set.
597
+ #
598
+ # @param [String] key
599
+ # @return [Array<String>]
600
+ def smembers(key)
601
+ synchronize do
602
+ @client.call [:smembers, key]
603
+ end
604
+ end
605
+
606
+ # Determine if a given value is a member of a set.
607
+ #
608
+ # @param [String] key
609
+ # @param [String] member
610
+ # @return [Boolean]
611
+ def sismember(key, member)
612
+ synchronize do
613
+ @client.call [:sismember, key, member], &_boolify
614
+ end
615
+ end
616
+
617
+ # Add one or more members to a set.
618
+ #
619
+ # @param [String] key
620
+ # @param [String, Array<String>] member one member, or array of members
621
+ # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
622
+ # holding whether or not adding the member succeeded, or `Fixnum` when an
623
+ # array of members is specified, holding the number of members that were
624
+ # successfully added
625
+ def sadd(key, member)
626
+ synchronize do
627
+ @client.call [:sadd, key, member] do |reply|
628
+ if member.is_a? Array
629
+ # Variadic: return integer
630
+ reply
631
+ else
632
+ # Single argument: return boolean
633
+ _boolify.call(reply)
634
+ end
635
+ end
636
+ end
637
+ end
638
+
639
+ # Remove one or more members from a set.
640
+ #
641
+ # @param [String] key
642
+ # @param [String, Array<String>] member one member, or array of members
643
+ # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
644
+ # holding whether or not removing the member succeeded, or `Fixnum` when an
645
+ # array of members is specified, holding the number of members that were
646
+ # successfully removed
647
+ def srem(key, member)
648
+ synchronize do
649
+ @client.call [:srem, key, member] do |reply|
650
+ if member.is_a? Array
651
+ # Variadic: return integer
652
+ reply
653
+ else
654
+ # Single argument: return boolean
655
+ _boolify.call(reply)
656
+ end
657
+ end
658
+ end
659
+ end
660
+
661
+ # Move a member from one set to another.
662
+ #
663
+ # @param [String] source source key
664
+ # @param [String] destination destination key
665
+ # @param [String] member member to move from `source` to `destination`
666
+ # @return [Boolean]
667
+ def smove(source, destination, member)
668
+ synchronize do
669
+ @client.call [:smove, source, destination, member], &_boolify
670
+ end
671
+ end
672
+
673
+ # Remove and return a random member from a set.
674
+ #
675
+ # @param [String] key
676
+ # @return [String]
677
+ def spop(key)
678
+ synchronize do
679
+ @client.call [:spop, key]
680
+ end
681
+ end
682
+
683
+ # Get the number of members in a set.
684
+ #
685
+ # @param [String] key
686
+ # @return [Fixnum]
687
+ def scard(key)
688
+ synchronize do
689
+ @client.call [:scard, key]
690
+ end
691
+ end
692
+
693
+ # Intersect multiple sets.
694
+ #
695
+ # @param [String, Array<String>] keys keys pointing to sets to intersect
696
+ # @return [Array<String>] members in the intersection
697
+ def sinter(*keys)
698
+ synchronize do
699
+ @client.call [:sinter, *keys]
700
+ end
701
+ end
702
+
703
+ # Intersect multiple sets and store the resulting set in a key.
704
+ #
705
+ # @param [String] destination destination key
706
+ # @param [String, Array<String>] keys keys pointing to sets to intersect
707
+ # @return [Fixnum] number of elements in the resulting set
708
+ def sinterstore(destination, *keys)
709
+ synchronize do
710
+ @client.call [:sinterstore, destination, *keys]
711
+ end
712
+ end
713
+
714
+ # Add multiple sets.
715
+ #
716
+ # @param [String, Array<String>] keys keys pointing to sets to unify
717
+ # @return [Array<String>] members in the union
718
+ def sunion(*keys)
719
+ synchronize do
720
+ @client.call [:sunion, *keys]
721
+ end
722
+ end
723
+
724
+ # Add multiple sets and store the resulting set in a key.
725
+ #
726
+ # @param [String] destination destination key
727
+ # @param [String, Array<String>] keys keys pointing to sets to unify
728
+ # @return [Fixnum] number of elements in the resulting set
729
+ def sunionstore(destination, *keys)
730
+ synchronize do
731
+ @client.call [:sunionstore, destination, *keys]
732
+ end
733
+ end
734
+
735
+ # Subtract multiple sets.
736
+ #
737
+ # @param [String, Array<String>] keys keys pointing to sets to subtract
738
+ # @return [Array<String>] members in the difference
739
+ def sdiff(*keys)
740
+ synchronize do
741
+ @client.call [:sdiff, *keys]
742
+ end
743
+ end
744
+
745
+ # Subtract multiple sets and store the resulting set in a key.
746
+ #
747
+ # @param [String] destination destination key
748
+ # @param [String, Array<String>] keys keys pointing to sets to subtract
749
+ # @return [Fixnum] number of elements in the resulting set
750
+ def sdiffstore(destination, *keys)
751
+ synchronize do
752
+ @client.call [:sdiffstore, destination, *keys]
753
+ end
754
+ end
755
+
756
+ # Get a random member from a set.
757
+ #
758
+ # @param [String] key
759
+ # @return [String]
760
+ def srandmember(key)
761
+ synchronize do
762
+ @client.call [:srandmember, key]
763
+ end
764
+ end
765
+
766
+ # Add one or more members to a sorted set, or update the score for members
767
+ # that already exist.
768
+ #
769
+ # @example Add a single `(score, member)` pair to a sorted set
770
+ # redis.zadd("zset", 32.0, "member")
771
+ # @example Add an array of `(score, member)` pairs to a sorted set
772
+ # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]])
773
+ #
774
+ # @param [String] key
775
+ # @param [(Float, String), Array<(Float,String)>] args
776
+ # - a single `(score, member)` pair
777
+ # - an array of `(score, member)` pairs
778
+ #
779
+ # @return [Boolean, Fixnum]
780
+ # - `Boolean` when a single pair is specified, holding whether or not it was
781
+ # **added** to the sorted set
782
+ # - `Fixnum` when an array of pairs is specified, holding the number of
783
+ # pairs that were **added** to the sorted set
784
+ def zadd(key, *args)
785
+ synchronize do
786
+ if args.size == 1 && args[0].is_a?(Array)
787
+ # Variadic: return integer
788
+ @client.call [:zadd, key] + args[0]
789
+ elsif args.size == 2
790
+ # Single pair: return boolean
791
+ @client.call [:zadd, key, args[0], args[1]], &_boolify
792
+ else
793
+ raise ArgumentError, "wrong number of arguments"
794
+ end
795
+ end
796
+ end
797
+
798
+ # Remove one or more members from a sorted set.
799
+ #
800
+ # @example Remove a single member from a sorted set
801
+ # redis.zrem("zset", "a")
802
+ # @example Remove an array of members from a sorted set
803
+ # redis.zrem("zset", ["a", "b"])
804
+ #
805
+ # @param [String] key
806
+ # @param [String, Array<String>] member
807
+ # - a single member
808
+ # - an array of members
809
+ #
810
+ # @return [Boolean, Fixnum]
811
+ # - `Boolean` when a single member is specified, holding whether or not it
812
+ # was removed from the sorted set
813
+ # - `Fixnum` when an array of pairs is specified, holding the number of
814
+ # members that were removed to the sorted set
815
+ def zrem(key, member)
816
+ synchronize do
817
+ @client.call [:zrem, key, member] do |reply|
818
+ if member.is_a? Array
819
+ # Variadic: return integer
820
+ reply
821
+ else
822
+ # Single argument: return boolean
823
+ _boolify.call(reply)
824
+ end
825
+ end
826
+ end
827
+ end
828
+
829
+ # Determine the index of a member in a sorted set.
830
+ #
831
+ # @param [String] key
832
+ # @param [String] member
833
+ # @return [Fixnum]
834
+ def zrank(key, member)
835
+ synchronize do
836
+ @client.call [:zrank, key, member]
837
+ end
838
+ end
839
+
840
+ # Determine the index of a member in a sorted set, with scores ordered from
841
+ # high to low.
842
+ #
843
+ # @param [String] key
844
+ # @param [String] member
845
+ # @return [Fixnum]
846
+ def zrevrank(key, member)
847
+ synchronize do
848
+ @client.call [:zrevrank, key, member]
849
+ end
850
+ end
851
+
852
+ # Increment the score of a member in a sorted set.
853
+ #
854
+ # @example
855
+ # redis.zincrby("zset", 32.0, "a")
856
+ # # => 64.0
857
+ #
858
+ # @param [String] key
859
+ # @param [Float] increment
860
+ # @param [String] member
861
+ # @return [Float] score of the member after incrementing it
862
+ def zincrby(key, increment, member)
863
+ synchronize do
864
+ @client.call [:zincrby, key, increment, member] do |reply|
865
+ Float(reply) if reply
866
+ end
867
+ end
868
+ end
869
+
870
+ # Get the number of members in a sorted set.
871
+ #
872
+ # @example
873
+ # redis.zcard("zset")
874
+ # # => 4
875
+ #
876
+ # @param [String] key
877
+ # @return [Fixnum]
878
+ def zcard(key)
879
+ synchronize do
880
+ @client.call [:zcard, key]
881
+ end
882
+ end
883
+
884
+ # Return a range of members in a sorted set, by index.
885
+ #
886
+ # @example Retrieve all members from a sorted set
887
+ # redis.zrange("zset", 0, -1)
888
+ # # => ["a", "b"]
889
+ # @example Retrieve all members and their scores from a sorted set
890
+ # redis.zrange("zset", 0, -1, :with_scores => true)
891
+ # # => [["a", 32.0], ["b", 64.0]]
892
+ #
893
+ # @param [String] key
894
+ # @param [Fixnum] start start index
895
+ # @param [Fixnum] stop stop index
896
+ # @param [Hash] options
897
+ # - `:with_scores => true`: include scores in output
898
+ #
899
+ # @return [Array<String>, Array<(String, Float)>]
900
+ # - when `:with_scores` is not specified, an array of members
901
+ # - when `:with_scores` is specified, an array with `(member, score)` pairs
902
+ def zrange(key, start, stop, options = {})
903
+ args = []
904
+
905
+ with_scores = options[:with_scores] || options[:withscores]
906
+ args << "WITHSCORES" if with_scores
907
+
908
+ synchronize do
909
+ @client.call [:zrange, key, start, stop, *args] do |reply|
910
+ if with_scores
911
+ if reply
912
+ reply.each_slice(2).map do |member, score|
913
+ [member, Float(score)]
914
+ end
915
+ end
916
+ else
917
+ reply
918
+ end
919
+ end
920
+ end
921
+ end
922
+
923
+ # Return a range of members in a sorted set, by index, with scores ordered
924
+ # from high to low.
925
+ #
926
+ # @example Retrieve all members from a sorted set
927
+ # redis.zrevrange("zset", 0, -1)
928
+ # # => ["b", "a"]
929
+ # @example Retrieve all members and their scores from a sorted set
930
+ # redis.zrevrange("zset", 0, -1, :with_scores => true)
931
+ # # => [["b", 64.0], ["a", 32.0]]
932
+ #
933
+ # @see #zrange
934
+ def zrevrange(key, start, stop, options = {})
935
+ args = []
936
+
937
+ with_scores = options[:with_scores] || options[:withscores]
938
+ args << "WITHSCORES" if with_scores
939
+
940
+ synchronize do
941
+ @client.call [:zrevrange, key, start, stop, *args] do |reply|
942
+ if with_scores
943
+ if reply
944
+ reply.each_slice(2).map do |member, score|
945
+ [member, Float(score)]
946
+ end
947
+ end
948
+ else
949
+ reply
950
+ end
951
+ end
952
+ end
953
+ end
954
+
955
+ # Return a range of members in a sorted set, by score.
956
+ #
957
+ # @example Retrieve members with score `>= 5` and `< 100`
958
+ # redis.zrangebyscore("zset", "5", "(100")
959
+ # # => ["a", "b"]
960
+ # @example Retrieve the first 2 members with score `>= 0`
961
+ # redis.zrangebyscore("zset", "0", "+inf", :limit => [0, 2])
962
+ # # => ["a", "b"]
963
+ # @example Retrieve members and their scores with scores `> 5`
964
+ # redis.zrangebyscore("zset", "(5", "+inf", :with_scores => true)
965
+ # # => [["a", 32.0], ["b", 64.0]]
966
+ #
967
+ # @param [String] key
968
+ # @param [String] min
969
+ # - inclusive minimum score is specified verbatim
970
+ # - exclusive minimum score is specified by prefixing `(`
971
+ # @param [String] max
972
+ # - inclusive maximum score is specified verbatim
973
+ # - exclusive maximum score is specified by prefixing `(`
974
+ # @param [Hash] options
975
+ # - `:with_scores => true`: include scores in output
976
+ # - `:limit => [offset, count]`: skip `offset` members, return a maximum of
977
+ # `count` members
978
+ #
979
+ # @return [Array<String>, Array<(String, Float)>]
980
+ # - when `:with_scores` is not specified, an array of members
981
+ # - when `:with_scores` is specified, an array with `(member, score)` pairs
982
+ def zrangebyscore(key, min, max, options = {})
983
+ args = []
984
+
985
+ with_scores = options[:with_scores] || options[:withscores]
986
+ args.concat ["WITHSCORES"] if with_scores
987
+
988
+ limit = options[:limit]
989
+ args.concat ["LIMIT", *limit] if limit
990
+
991
+ synchronize do
992
+ @client.call [:zrangebyscore, key, min, max, *args] do |reply|
993
+ if with_scores
994
+ if reply
995
+ reply.each_slice(2).map do |member, score|
996
+ [member, Float(score)]
997
+ end
998
+ end
999
+ else
1000
+ reply
1001
+ end
1002
+ end
1003
+ end
1004
+ end
1005
+
1006
+ # Return a range of members in a sorted set, by score, with scores ordered
1007
+ # from high to low.
1008
+ #
1009
+ # @example Retrieve members with score `< 100` and `>= 5`
1010
+ # redis.zrevrangebyscore("zset", "(100", "5")
1011
+ # # => ["b", "a"]
1012
+ # @example Retrieve the first 2 members with score `<= 0`
1013
+ # redis.zrevrangebyscore("zset", "0", "-inf", :limit => [0, 2])
1014
+ # # => ["b", "a"]
1015
+ # @example Retrieve members and their scores with scores `> 5`
1016
+ # redis.zrevrangebyscore("zset", "+inf", "(5", :with_scores => true)
1017
+ # # => [["b", 64.0], ["a", 32.0]]
1018
+ #
1019
+ # @see #zrangebyscore
1020
+ def zrevrangebyscore(key, max, min, options = {})
1021
+ args = []
1022
+
1023
+ with_scores = options[:with_scores] || options[:withscores]
1024
+ args.concat ["WITHSCORES"] if with_scores
1025
+
1026
+ limit = options[:limit]
1027
+ args.concat ["LIMIT", *limit] if limit
1028
+
1029
+ synchronize do
1030
+ @client.call [:zrevrangebyscore, key, max, min, *args] do |reply|
1031
+ if with_scores
1032
+ if reply
1033
+ reply.each_slice(2).map do |member, score|
1034
+ [member, Float(score)]
1035
+ end
1036
+ end
1037
+ else
1038
+ reply
1039
+ end
1040
+ end
1041
+ end
1042
+ end
1043
+
1044
+ # Count the members in a sorted set with scores within the given values.
1045
+ #
1046
+ # @example Count members with score `>= 5` and `< 100`
1047
+ # redis.zcount("zset", "5", "(100")
1048
+ # # => 2
1049
+ # @example Count members with scores `> 5`
1050
+ # redis.zcount("zset", "(5", "+inf")
1051
+ # # => 2
1052
+ #
1053
+ # @param [String] key
1054
+ # @param [String] min
1055
+ # - inclusive minimum score is specified verbatim
1056
+ # - exclusive minimum score is specified by prefixing `(`
1057
+ # @param [String] max
1058
+ # - inclusive maximum score is specified verbatim
1059
+ # - exclusive maximum score is specified by prefixing `(`
1060
+ # @return [Fixnum] number of members in within the specified range
1061
+ def zcount(key, start, stop)
1062
+ synchronize do
1063
+ @client.call [:zcount, key, start, stop]
1064
+ end
1065
+ end
1066
+
1067
+ # Remove all members in a sorted set within the given scores.
1068
+ #
1069
+ # @example Remove members with score `>= 5` and `< 100`
1070
+ # redis.zremrangebyscore("zset", "5", "(100")
1071
+ # # => 2
1072
+ # @example Remove members with scores `> 5`
1073
+ # redis.zremrangebyscore("zset", "(5", "+inf")
1074
+ # # => 2
1075
+ #
1076
+ # @param [String] key
1077
+ # @param [String] min
1078
+ # - inclusive minimum score is specified verbatim
1079
+ # - exclusive minimum score is specified by prefixing `(`
1080
+ # @param [String] max
1081
+ # - inclusive maximum score is specified verbatim
1082
+ # - exclusive maximum score is specified by prefixing `(`
1083
+ # @return [Fixnum] number of members that were removed
1084
+ def zremrangebyscore(key, min, max)
1085
+ synchronize do
1086
+ @client.call [:zremrangebyscore, key, min, max]
1087
+ end
1088
+ end
1089
+
1090
+ # Remove all members in a sorted set within the given indexes.
1091
+ #
1092
+ # @example Remove first 5 members
1093
+ # redis.zremrangebyrank("zset", 0, 4)
1094
+ # # => 5
1095
+ # @example Remove last 5 members
1096
+ # redis.zremrangebyrank("zset", -5, -1)
1097
+ # # => 5
1098
+ #
1099
+ # @param [String] key
1100
+ # @param [Fixnum] start start index
1101
+ # @param [Fixnum] stop stop index
1102
+ # @return [Fixnum] number of members that were removed
1103
+ def zremrangebyrank(key, start, stop)
1104
+ synchronize do
1105
+ @client.call [:zremrangebyrank, key, start, stop]
1106
+ end
1107
+ end
1108
+
1109
+ # Get the score associated with the given member in a sorted set.
1110
+ #
1111
+ # @example Get the score for member "a"
1112
+ # redis.zscore("zset", "a")
1113
+ # # => 32.0
1114
+ #
1115
+ # @param [String] key
1116
+ # @param [String] member
1117
+ # @return [Float] score of the member
1118
+ def zscore(key, member)
1119
+ synchronize do
1120
+ @client.call [:zscore, key, member] do |reply|
1121
+ Float(reply) if reply
1122
+ end
1123
+ end
1124
+ end
1125
+
1126
+ # Intersect multiple sorted sets and store the resulting sorted set in a new
1127
+ # key.
1128
+ #
1129
+ # @example Compute the intersection of `2*zsetA` with `1*zsetB`, summing their scores
1130
+ # redis.zinterstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
1131
+ # # => 4
1132
+ #
1133
+ # @param [String] destination destination key
1134
+ # @param [Array<String>] keys source keys
1135
+ # @param [Hash] options
1136
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
1137
+ # sorted sets
1138
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1139
+ # @return [Fixnum] number of elements in the resulting sorted set
1140
+ def zinterstore(destination, keys, options = {})
1141
+ command = CommandOptions.new(options) do |c|
1142
+ c.splat :weights
1143
+ c.value :aggregate
1144
+ end
1145
+
1146
+ synchronize do
1147
+ @client.call [:zinterstore, destination, keys.size, *(keys + command.to_a)]
1148
+ end
1149
+ end
1150
+
1151
+ # Add multiple sorted sets and store the resulting sorted set in a new key.
1152
+ #
1153
+ # @example Compute the union of `2*zsetA` with `1*zsetB`, summing their scores
1154
+ # redis.zunionstore("zsetC", ["zsetA", "zsetB"], :weights => [2.0, 1.0], :aggregate => "sum")
1155
+ # # => 8
1156
+ #
1157
+ # @param [String] destination destination key
1158
+ # @param [Array<String>] keys source keys
1159
+ # @param [Hash] options
1160
+ # - `:weights => [Float, Float, ...]`: weights to associate with source
1161
+ # sorted sets
1162
+ # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1163
+ # @return [Fixnum] number of elements in the resulting sorted set
1164
+ def zunionstore(destination, keys, options = {})
1165
+ command = CommandOptions.new(options) do |c|
1166
+ c.splat :weights
1167
+ c.value :aggregate
1168
+ end
1169
+
1170
+ synchronize do
1171
+ @client.call [:zunionstore, destination, keys.size, *(keys + command.to_a)]
1172
+ end
1173
+ end
1174
+
1175
+ # Move a key to another database.
1176
+ #
1177
+ # @example Move a key to another database
1178
+ # redis.set "foo", "bar"
1179
+ # # => "OK"
1180
+ # redis.move "foo", 2
1181
+ # # => true
1182
+ # redis.exists "foo"
1183
+ # # => false
1184
+ # redis.select 2
1185
+ # # => "OK"
1186
+ # redis.exists "foo"
1187
+ # # => true
1188
+ # resis.get "foo"
1189
+ # # => "bar"
1190
+ #
1191
+ # @param [String] key
1192
+ # @param [Fixnum] db
1193
+ # @return [Boolean] whether the key was moved or not
1194
+ def move(key, db)
1195
+ synchronize do
1196
+ @client.call [:move, key, db], &_boolify
1197
+ end
1198
+ end
1199
+
1200
+ # Set the value of a key, only if the key does not exist.
1201
+ #
1202
+ # @param [String] key
1203
+ # @param [String] value
1204
+ # @return [Boolean] whether the key was set or not
1205
+ def setnx(key, value)
1206
+ synchronize do
1207
+ @client.call [:setnx, key, value], &_boolify
1208
+ end
1209
+ end
1210
+
1211
+ # Delete one or more keys.
1212
+ #
1213
+ # @param [String, Array<String>] keys
1214
+ # @return [Fixnum] number of keys that were removed
1215
+ def del(*keys)
1216
+ synchronize do
1217
+ @client.call [:del, *keys]
1218
+ end
1219
+ end
1220
+
1221
+ # Rename a key. If the new key already exists it is overwritten.
1222
+ #
1223
+ # @param [String] old_name
1224
+ # @param [String] new_name
1225
+ # @return [String] `OK`
1226
+ def rename(old_name, new_name)
1227
+ synchronize do
1228
+ @client.call [:rename, old_name, new_name]
1229
+ end
1230
+ end
1231
+
1232
+ # Rename a key, only if the new key does not exist.
1233
+ #
1234
+ # @param [String] old_name
1235
+ # @param [String] new_name
1236
+ # @return [Boolean] whether the key was renamed or not
1237
+ def renamenx(old_name, new_name)
1238
+ synchronize do
1239
+ @client.call [:renamenx, old_name, new_name], &_boolify
1240
+ end
1241
+ end
1242
+
1243
+ # Set a key's time to live in seconds.
1244
+ #
1245
+ # @param [String] key
1246
+ # @param [Fixnum] seconds time to live. After this timeout has expired,
1247
+ # the key will automatically be deleted
1248
+ # @return [Boolean] whether the timeout was set or not
1249
+ def expire(key, seconds)
1250
+ synchronize do
1251
+ @client.call [:expire, key, seconds], &_boolify
1252
+ end
1253
+ end
1254
+
1255
+ # Remove the expiration from a key.
1256
+ #
1257
+ # @param [String] key
1258
+ # @return [Boolean] whether the timeout was removed or not
1259
+ def persist(key)
1260
+ synchronize do
1261
+ @client.call [:persist, key], &_boolify
1262
+ end
1263
+ end
1264
+
1265
+ # Get the time to live for a key.
1266
+ #
1267
+ # @param [String] key
1268
+ # @return [Fixnum] remaining time to live in seconds, or -1 if the
1269
+ # key does not exist or does not have a timeout
1270
+ def ttl(key)
1271
+ synchronize do
1272
+ @client.call [:ttl, key]
1273
+ end
1274
+ end
1275
+
1276
+ # Set the expiration for a key as a UNIX timestamp.
1277
+ #
1278
+ # @param [String] key
1279
+ # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp
1280
+ # (seconds since January 1, 1970). After this timeout has expired,
1281
+ # the key will automatically be deleted
1282
+ # @return [Boolean] whether the timeout was set or not
1283
+ def expireat(key, unix_time)
1284
+ synchronize do
1285
+ @client.call [:expireat, key, unix_time], &_boolify
1286
+ end
1287
+ end
1288
+
1289
+ # Set the string value of a hash field.
1290
+ def hset(key, field, value)
1291
+ synchronize do
1292
+ @client.call [:hset, key, field, value], &_boolify
1293
+ end
1294
+ end
1295
+
1296
+ # Set the value of a hash field, only if the field does not exist.
1297
+ def hsetnx(key, field, value)
1298
+ synchronize do
1299
+ @client.call [:hsetnx, key, field, value], &_boolify
1300
+ end
1301
+ end
1302
+
1303
+ # Set multiple hash fields to multiple values.
1304
+ def hmset(key, *attrs)
1305
+ synchronize do
1306
+ @client.call [:hmset, key, *attrs]
1307
+ end
1308
+ end
1309
+
1310
+ def mapped_hmset(key, hash)
1311
+ hmset(key, *hash.to_a.flatten)
1312
+ end
1313
+
1314
+ # Get the values of all the given hash fields.
1315
+ def hmget(key, *fields, &blk)
1316
+ synchronize do
1317
+ @client.call [:hmget, key, *fields], &blk
1318
+ end
1319
+ end
1320
+
1321
+ def mapped_hmget(key, *fields)
1322
+ hmget(key, *fields) do |reply|
1323
+ if reply.kind_of?(Array)
1324
+ hash = Hash.new
1325
+ fields.zip(reply).each do |field, value|
1326
+ hash[field] = value
1327
+ end
1328
+ hash
1329
+ else
1330
+ reply
1331
+ end
1332
+ end
1333
+ end
1334
+
1335
+ # Get the number of fields in a hash.
1336
+ def hlen(key)
1337
+ synchronize do
1338
+ @client.call [:hlen, key]
1339
+ end
1340
+ end
1341
+
1342
+ # Get all the values in a hash.
1343
+ def hvals(key)
1344
+ synchronize do
1345
+ @client.call [:hvals, key]
1346
+ end
1347
+ end
1348
+
1349
+ # Increment the integer value of a hash field by the given number.
1350
+ def hincrby(key, field, increment)
1351
+ synchronize do
1352
+ @client.call [:hincrby, key, field, increment]
1353
+ end
1354
+ end
1355
+
1356
+ # Discard all commands issued after MULTI.
1357
+ def discard
1358
+ synchronize do
1359
+ @client.call [:discard]
1360
+ end
1361
+ end
1362
+
1363
+ # Determine if a hash field exists.
1364
+ def hexists(key, field)
1365
+ synchronize do
1366
+ @client.call [:hexists, key, field], &_boolify
1367
+ end
1368
+ end
1369
+
1370
+ # Listen for all requests received by the server in real time.
1371
+ def monitor(&block)
1372
+ synchronize do
1373
+ @client.call_loop([:monitor], &block)
1374
+ end
1375
+ end
1376
+
1377
+ def debug(*args)
1378
+ synchronize do
1379
+ @client.call [:debug, *args]
1380
+ end
1381
+ end
1382
+
1383
+ def object(*args)
1384
+ synchronize do
1385
+ @client.call [:object, *args]
1386
+ end
1387
+ end
1388
+
1389
+ # Internal command used for replication.
1390
+ def sync
1391
+ synchronize do
1392
+ @client.call [:sync]
1393
+ end
1394
+ end
1395
+
1396
+ # Set the string value of a key.
1397
+ def set(key, value)
1398
+ synchronize do
1399
+ @client.call [:set, key, value]
1400
+ end
1401
+ end
1402
+
1403
+ alias :[]= :set
1404
+
1405
+ # Sets or clears the bit at offset in the string value stored at key.
1406
+ def setbit(key, offset, value)
1407
+ synchronize do
1408
+ @client.call [:setbit, key, offset, value]
1409
+ end
1410
+ end
1411
+
1412
+ # Set the value and expiration of a key.
1413
+ def setex(key, ttl, value)
1414
+ synchronize do
1415
+ @client.call [:setex, key, ttl, value]
1416
+ end
1417
+ end
1418
+
1419
+ # Overwrite part of a string at key starting at the specified offset.
1420
+ def setrange(key, offset, value)
1421
+ synchronize do
1422
+ @client.call [:setrange, key, offset, value]
1423
+ end
1424
+ end
1425
+
1426
+ # Set multiple keys to multiple values.
1427
+ def mset(*args)
1428
+ synchronize do
1429
+ @client.call [:mset, *args]
1430
+ end
1431
+ end
1432
+
1433
+ def mapped_mset(hash)
1434
+ mset(*hash.to_a.flatten)
1435
+ end
1436
+
1437
+ # Set multiple keys to multiple values, only if none of the keys exist.
1438
+ def msetnx(*args)
1439
+ synchronize do
1440
+ @client.call [:msetnx, *args]
1441
+ end
1442
+ end
1443
+
1444
+ def mapped_msetnx(hash)
1445
+ msetnx(*hash.to_a.flatten)
1446
+ end
1447
+
1448
+ def mapped_mget(*keys)
1449
+ mget(*keys) do |reply|
1450
+ if reply.kind_of?(Array)
1451
+ hash = Hash.new
1452
+ keys.zip(reply).each do |field, value|
1453
+ hash[field] = value
1454
+ end
1455
+ hash
1456
+ else
1457
+ reply
1458
+ end
1459
+ end
1460
+ end
1461
+
1462
+ # Sort the elements in a list, set or sorted set.
1463
+ def sort(key, options = {})
1464
+ command = CommandOptions.new(options) do |c|
1465
+ c.value :by
1466
+ c.splat :limit
1467
+ c.multi :get
1468
+ c.words :order
1469
+ c.value :store
1470
+ end
1471
+
1472
+ synchronize do
1473
+ @client.call [:sort, key, *command.to_a]
1474
+ end
1475
+ end
1476
+
1477
+ # Increment the integer value of a key by one.
1478
+ def incr(key)
1479
+ synchronize do
1480
+ @client.call [:incr, key]
1481
+ end
1482
+ end
1483
+
1484
+ # Increment the integer value of a key by the given number.
1485
+ def incrby(key, increment)
1486
+ synchronize do
1487
+ @client.call [:incrby, key, increment]
1488
+ end
1489
+ end
1490
+
1491
+ # Decrement the integer value of a key by one.
1492
+ def decr(key)
1493
+ synchronize do
1494
+ @client.call [:decr, key]
1495
+ end
1496
+ end
1497
+
1498
+ # Decrement the integer value of a key by the given number.
1499
+ def decrby(key, decrement)
1500
+ synchronize do
1501
+ @client.call [:decrby, key, decrement]
1502
+ end
1503
+ end
1504
+
1505
+ # Determine the type stored at key.
1506
+ #
1507
+ # @param [String] key
1508
+ # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
1509
+ def type(key)
1510
+ synchronize do
1511
+ @client.call [:type, key]
1512
+ end
1513
+ end
1514
+
1515
+ # Close the connection.
1516
+ def quit
1517
+ synchronize do
1518
+ begin
1519
+ @client.call [:quit]
1520
+ rescue ConnectionError
1521
+ ensure
1522
+ @client.disconnect
1523
+ end
1524
+ end
1525
+ end
1526
+
1527
+ # Synchronously save the dataset to disk and then shut down the server.
1528
+ def shutdown
1529
+ synchronize do
1530
+ @client.without_reconnect do
1531
+ begin
1532
+ @client.call [:shutdown]
1533
+ rescue ConnectionError
1534
+ # This means Redis has probably exited.
1535
+ nil
1536
+ end
1537
+ end
1538
+ end
1539
+ end
1540
+
1541
+ # Make the server a slave of another instance, or promote it as master.
1542
+ def slaveof(host, port)
1543
+ synchronize do
1544
+ @client.call [:slaveof, host, port]
1545
+ end
1546
+ end
1547
+
1548
+ def pipelined
1549
+ synchronize do
1550
+ begin
1551
+ original, @client = @client, Pipeline.new
1552
+ yield
1553
+ original.call_pipeline(@client)
1554
+ ensure
1555
+ @client = original
1556
+ end
1557
+ end
1558
+ end
1559
+
1560
+ # Watch the given keys to determine execution of the MULTI/EXEC block.
1561
+ def watch(*keys)
1562
+ synchronize do
1563
+ @client.call [:watch, *keys]
1564
+ end
1565
+ end
1566
+
1567
+ # Forget about all watched keys.
1568
+ def unwatch
1569
+ synchronize do
1570
+ @client.call [:unwatch]
1571
+ end
1572
+ end
1573
+
1574
+ # Execute all commands issued after MULTI.
1575
+ def exec
1576
+ synchronize do
1577
+ @client.call [:exec]
1578
+ end
1579
+ end
1580
+
1581
+ # Mark the start of a transaction block.
1582
+ def multi
1583
+ synchronize do
1584
+ if !block_given?
1585
+ @client.call [:multi]
1586
+ else
1587
+ begin
1588
+ pipeline = Pipeline::Multi.new
1589
+ original, @client = @client, pipeline
1590
+ yield(self)
1591
+ original.call_pipeline(pipeline)
1592
+ ensure
1593
+ @client = original
1594
+ end
1595
+ end
1596
+ end
1597
+ end
1598
+
1599
+ # Post a message to a channel.
1600
+ def publish(channel, message)
1601
+ synchronize do
1602
+ @client.call [:publish, channel, message]
1603
+ end
1604
+ end
1605
+
1606
+ def subscribed?
1607
+ synchronize do
1608
+ @client.kind_of? SubscribedClient
1609
+ end
1610
+ end
1611
+
1612
+ # Stop listening for messages posted to the given channels.
1613
+ def unsubscribe(*channels)
1614
+ synchronize do
1615
+ raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
1616
+ @client.unsubscribe(*channels)
1617
+ end
1618
+ end
1619
+
1620
+ # Stop listening for messages posted to channels matching the given patterns.
1621
+ def punsubscribe(*channels)
1622
+ synchronize do
1623
+ raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
1624
+ @client.punsubscribe(*channels)
1625
+ end
1626
+ end
1627
+
1628
+ # Listen for messages published to the given channels.
1629
+ def subscribe(*channels, &block)
1630
+ synchronize do
1631
+ subscription(:subscribe, channels, block)
1632
+ end
1633
+ end
1634
+
1635
+ # Listen for messages published to channels matching the given patterns.
1636
+ def psubscribe(*channels, &block)
1637
+ synchronize do
1638
+ subscription(:psubscribe, channels, block)
1639
+ end
1640
+ end
1641
+
1642
+ def id
1643
+ synchronize do
1644
+ @client.id
1645
+ end
1646
+ end
1647
+
1648
+ def inspect
1649
+ synchronize do
1650
+ "#<Redis client v#{Redis::VERSION} connected to #{id} (Redis v#{info["redis_version"]})>"
1651
+ end
1652
+ end
1653
+
1654
+ def method_missing(command, *args)
1655
+ synchronize do
1656
+ @client.call [command, *args]
1657
+ end
1658
+ end
1659
+
1660
+ class CommandOptions
1661
+ def initialize(options)
1662
+ @result = []
1663
+ @options = options
1664
+ yield(self)
1665
+ end
1666
+
1667
+ def bool(name)
1668
+ insert(name) { |argument, value| [argument] }
1669
+ end
1670
+
1671
+ def value(name)
1672
+ insert(name) { |argument, value| [argument, value] }
1673
+ end
1674
+
1675
+ def splat(name)
1676
+ insert(name) { |argument, value| [argument, *value] }
1677
+ end
1678
+
1679
+ def multi(name)
1680
+ insert(name) { |argument, value| [argument].product(Array(value)).flatten }
1681
+ end
1682
+
1683
+ def words(name)
1684
+ insert(name) { |argument, value| value.split(" ") }
1685
+ end
1686
+
1687
+ def to_a
1688
+ @result
1689
+ end
1690
+
1691
+ def insert(name)
1692
+ @result += yield(name.to_s.upcase.gsub("_", ""), @options[name]) if @options[name]
1693
+ end
1694
+ end
1695
+
1696
+ private
1697
+
1698
+ # Commands returning 1 for true and 0 for false may be executed in a pipeline
1699
+ # where the method call will return nil. Propagate the nil instead of falsely
1700
+ # returning false.
1701
+ def _boolify
1702
+ lambda { |value|
1703
+ value == 1 if value
1704
+ }
1705
+ end
1706
+
1707
+ def subscription(method, channels, block)
1708
+ return @client.call [method, *channels] if subscribed?
1709
+
1710
+ begin
1711
+ original, @client = @client, SubscribedClient.new(@client)
1712
+ @client.send(method, *channels, &block)
1713
+ ensure
1714
+ @client = original
1715
+ end
1716
+ end
1717
+
1718
+ end
1719
+
1720
+ require "redis/version"
1721
+ require "redis/connection"
1722
+ require "redis/client"
1723
+ require "redis/pipeline"
1724
+ require "redis/subscribe"