]>
Commit | Line | Data |
---|---|---|
ed9b544e | 1 | require 'socket' |
38210f7f | 2 | require 'set' |
3 | require File.join(File.dirname(__FILE__),'server') | |
57172ffb | 4 | require File.join(File.dirname(__FILE__),'pipeline') |
29fac617 | 5 | |
38210f7f | 6 | class RedisError < StandardError |
7 | end | |
8 | class RedisRenameError < StandardError | |
29fac617 | 9 | end |
d7fc9edb | 10 | |
ed9b544e | 11 | class Redis |
38210f7f | 12 | ERR = "-".freeze |
13 | OK = 'OK'.freeze | |
14 | PONG = 'PONG'.freeze | |
15 | SINGLE = '+'.freeze | |
16 | BULK = '$'.freeze | |
17 | MULTI = '*'.freeze | |
18 | INT = ':'.freeze | |
19 | ||
20 | attr_reader :server | |
21 | ||
22 | def initialize(opts={}) | |
23 | @opts = {:host => 'localhost', :port => '6379', :db => 0}.merge(opts) | |
24 | $debug = @opts[:debug] | |
25 | @db = @opts[:db] | |
26 | @server = Server.new(@opts[:host], @opts[:port], (@opts[:timeout]||10)) | |
27 | end | |
ed9b544e | 28 | |
38210f7f | 29 | def pipelined |
30 | pipeline = Pipeline.new(self) | |
31 | yield pipeline | |
32 | pipeline.finish | |
33 | end | |
29fac617 | 34 | |
38210f7f | 35 | def to_s |
36 | "#{host}:#{port} -> #{@db}" | |
37 | end | |
38 | ||
39 | def port | |
40 | @opts[:port] | |
41 | end | |
42 | ||
43 | def host | |
44 | @opts[:host] | |
45 | end | |
46 | ||
47 | def quit | |
48 | execute_command("QUIT\r\n", true) | |
49 | end | |
29fac617 | 50 | |
38210f7f | 51 | def ping |
52 | execute_command("PING\r\n") == PONG | |
53 | end | |
29fac617 | 54 | |
38210f7f | 55 | def select_db(index) |
56 | @db = index | |
57 | execute_command("SELECT #{index}\r\n") | |
58 | end | |
59 | ||
60 | def flush_db | |
61 | execute_command("FLUSHDB\r\n") == OK | |
62 | end | |
29fac617 | 63 | |
38210f7f | 64 | def flush_all |
65 | puts "Warning!\nFlushing *ALL* databases!\n5 Seconds to Hit ^C!" | |
66 | trap('INT') {quit; return false} | |
67 | sleep 5 | |
68 | execute_command("FLUSHALL\r\n") == OK | |
69 | end | |
70 | ||
71 | def last_save | |
72 | execute_command("LASTSAVE\r\n").to_i | |
73 | end | |
74 | ||
75 | def bgsave | |
76 | execute_command("BGSAVE\r\n") == OK | |
77 | end | |
78 | ||
79 | def info | |
80 | info = {} | |
81 | x = execute_command("INFO\r\n") | |
82 | x.each_line do |kv| | |
83 | k,v = kv.split(':', 2) | |
84 | k,v = k.chomp, v = v.chomp | |
85 | info[k.to_sym] = v | |
86 | end | |
87 | info | |
88 | end | |
89 | ||
90 | def keys(glob) | |
91 | execute_command("KEYS #{glob}\r\n").split(' ') | |
92 | end | |
29fac617 | 93 | |
38210f7f | 94 | def rename!(oldkey, newkey) |
95 | execute_command("RENAME #{oldkey} #{newkey}\r\n") | |
96 | end | |
97 | ||
98 | def rename(oldkey, newkey) | |
99 | case execute_command("RENAMENX #{oldkey} #{newkey}\r\n") | |
100 | when -1 | |
101 | raise RedisRenameError, "source key: #{oldkey} does not exist" | |
102 | when 0 | |
103 | raise RedisRenameError, "target key: #{oldkey} already exists" | |
104 | when -3 | |
105 | raise RedisRenameError, "source and destination keys are the same" | |
106 | when 1 | |
107 | true | |
69664139 | 108 | end |
38210f7f | 109 | end |
110 | ||
111 | def key?(key) | |
112 | execute_command("EXISTS #{key}\r\n") == 1 | |
113 | end | |
114 | ||
115 | def delete(key) | |
116 | execute_command("DEL #{key}\r\n") == 1 | |
117 | end | |
118 | ||
119 | def [](key) | |
120 | get(key) | |
121 | end | |
57172ffb | 122 | |
38210f7f | 123 | def get(key) |
124 | execute_command("GET #{key}\r\n") | |
125 | end | |
126 | ||
127 | def mget(*keys) | |
128 | execute_command("MGET #{keys.join(' ')}\r\n") | |
129 | end | |
130 | ||
131 | def incr(key, increment=nil) | |
132 | if increment | |
133 | execute_command("INCRBY #{key} #{increment}\r\n") | |
134 | else | |
135 | execute_command("INCR #{key}\r\n") | |
136 | end | |
137 | end | |
138 | ||
139 | def decr(key, decrement=nil) | |
140 | if decrement | |
141 | execute_command("DECRBY #{key} #{decrement}\r\n") | |
142 | else | |
143 | execute_command("DECR #{key}\r\n") | |
144 | end | |
145 | end | |
146 | ||
147 | def randkey | |
148 | execute_command("RANDOMKEY\r\n") | |
149 | end | |
150 | ||
151 | def list_length(key) | |
152 | case i = execute_command("LLEN #{key}\r\n") | |
153 | when -2 | |
154 | raise RedisError, "key: #{key} does not hold a list value" | |
155 | else | |
156 | i | |
69664139 | 157 | end |
38210f7f | 158 | end |
159 | ||
160 | def type?(key) | |
161 | execute_command("TYPE #{key}\r\n") | |
162 | end | |
163 | ||
164 | def push_tail(key, val) | |
165 | execute_command("RPUSH #{key} #{value_to_wire(val)}\r\n") | |
166 | end | |
167 | ||
168 | def push_head(key, val) | |
169 | execute_command("LPUSH #{key} #{value_to_wire(val)}\r\n") | |
170 | end | |
171 | ||
172 | def pop_head(key) | |
173 | execute_command("LPOP #{key}\r\n") | |
174 | end | |
175 | ||
176 | def pop_tail(key) | |
177 | execute_command("RPOP #{key}\r\n") | |
178 | end | |
179 | ||
180 | def list_set(key, index, val) | |
181 | execute_command("LSET #{key} #{index} #{value_to_wire(val)}\r\n") == OK | |
182 | end | |
29fac617 | 183 | |
38210f7f | 184 | def list_range(key, start, ending) |
185 | execute_command("LRANGE #{key} #{start} #{ending}\r\n") | |
186 | end | |
187 | ||
188 | def list_trim(key, start, ending) | |
189 | execute_command("LTRIM #{key} #{start} #{ending}\r\n") | |
190 | end | |
191 | ||
192 | def list_index(key, index) | |
193 | execute_command("LINDEX #{key} #{index}\r\n") | |
194 | end | |
195 | ||
196 | def list_rm(key, count, val) | |
197 | case num = execute_command("LREM #{key} #{count} #{value_to_wire(val)}\r\n") | |
198 | when -1 | |
199 | raise RedisError, "key: #{key} does not exist" | |
200 | when -2 | |
201 | raise RedisError, "key: #{key} does not hold a list value" | |
202 | else | |
203 | num | |
69664139 | 204 | end |
38210f7f | 205 | end |
d7fc9edb | 206 | |
38210f7f | 207 | def set_add(key, member) |
208 | case execute_command("SADD #{key} #{value_to_wire(member)}\r\n") | |
209 | when 1 | |
210 | true | |
211 | when 0 | |
212 | false | |
213 | when -2 | |
214 | raise RedisError, "key: #{key} contains a non set value" | |
69664139 | 215 | end |
38210f7f | 216 | end |
d7fc9edb | 217 | |
38210f7f | 218 | def set_delete(key, member) |
219 | case execute_command("SREM #{key} #{value_to_wire(member)}\r\n") | |
220 | when 1 | |
221 | true | |
222 | when 0 | |
223 | false | |
224 | when -2 | |
225 | raise RedisError, "key: #{key} contains a non set value" | |
69664139 | 226 | end |
38210f7f | 227 | end |
d7fc9edb | 228 | |
38210f7f | 229 | def set_count(key) |
230 | case i = execute_command("SCARD #{key}\r\n") | |
231 | when -2 | |
232 | raise RedisError, "key: #{key} contains a non set value" | |
233 | else | |
234 | i | |
d7fc9edb | 235 | end |
38210f7f | 236 | end |
69664139 | 237 | |
38210f7f | 238 | def set_member?(key, member) |
239 | case execute_command("SISMEMBER #{key} #{value_to_wire(member)}\r\n") | |
240 | when 1 | |
241 | true | |
242 | when 0 | |
243 | false | |
244 | when -2 | |
245 | raise RedisError, "key: #{key} contains a non set value" | |
ed9b544e | 246 | end |
38210f7f | 247 | end |
248 | ||
249 | def set_members(key) | |
250 | Set.new(execute_command("SMEMBERS #{key}\r\n")) | |
251 | end | |
252 | ||
253 | def set_intersect(*keys) | |
254 | Set.new(execute_command("SINTER #{keys.join(' ')}\r\n")) | |
255 | end | |
256 | ||
257 | def set_inter_store(destkey, *keys) | |
258 | execute_command("SINTERSTORE #{destkey} #{keys.join(' ')}\r\n") | |
259 | end | |
260 | ||
261 | def set_union(*keys) | |
262 | Set.new(execute_command("SUNION #{keys.join(' ')}\r\n")) | |
263 | end | |
264 | ||
265 | def set_union_store(destkey, *keys) | |
266 | execute_command("SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n") | |
267 | end | |
268 | ||
269 | def set_diff(*keys) | |
270 | Set.new(execute_command("SDIFF #{keys.join(' ')}\r\n")) | |
271 | end | |
272 | ||
273 | def set_diff_store(destkey, *keys) | |
274 | execute_command("SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n") | |
275 | end | |
276 | ||
277 | def set_move(srckey, destkey, member) | |
278 | execute_command("SMOVE #{srckey} #{destkey} #{value_to_wire(member)}\r\n") == 1 | |
279 | end | |
69664139 | 280 | |
38210f7f | 281 | def sort(key, opts={}) |
282 | cmd = "SORT #{key}" | |
283 | cmd << " BY #{opts[:by]}" if opts[:by] | |
284 | cmd << " GET #{[opts[:get]].flatten * ' GET '}" if opts[:get] | |
285 | cmd << " INCR #{opts[:incr]}" if opts[:incr] | |
286 | cmd << " DEL #{opts[:del]}" if opts[:del] | |
287 | cmd << " DECR #{opts[:decr]}" if opts[:decr] | |
288 | cmd << " #{opts[:order]}" if opts[:order] | |
289 | cmd << " LIMIT #{opts[:limit].join(' ')}" if opts[:limit] | |
290 | cmd << "\r\n" | |
291 | execute_command(cmd) | |
292 | end | |
293 | ||
294 | def []=(key, val) | |
295 | set(key,val) | |
296 | end | |
297 | ||
298 | def set(key, val, expiry=nil) | |
299 | s = execute_command("SET #{key} #{value_to_wire(val)}\r\n") == OK | |
300 | return expire(key, expiry) if s && expiry | |
301 | s | |
302 | end | |
303 | ||
304 | def dbsize | |
305 | execute_command("DBSIZE\r\n") | |
306 | end | |
307 | ||
308 | def expire(key, expiry=nil) | |
309 | execute_command("EXPIRE #{key} #{expiry}\r\n") == 1 | |
310 | end | |
311 | ||
312 | def set_unless_exists(key, val) | |
313 | execute_command("SETNX #{key} #{value_to_wire(val)}\r\n") == 1 | |
314 | end | |
315 | ||
316 | def bulk_reply | |
317 | begin | |
318 | x = read | |
319 | puts "bulk_reply read value is #{x.inspect}" if $debug | |
320 | return x | |
321 | rescue => e | |
322 | puts "error in bulk_reply #{e}" if $debug | |
323 | nil | |
ed9b544e | 324 | end |
38210f7f | 325 | end |
326 | ||
327 | def write(data) | |
328 | puts "writing: #{data}" if $debug | |
329 | @socket.write(data) | |
330 | end | |
331 | ||
332 | def read(length = 0) | |
333 | length = read_proto unless length > 0 | |
334 | res = @socket.read(length) | |
335 | puts "read is #{res.inspect}" if $debug | |
336 | res | |
337 | end | |
338 | ||
339 | def multi_bulk | |
340 | res = read_proto | |
341 | puts "mb res is #{res.inspect}" if $debug | |
342 | list = [] | |
343 | Integer(res).times do | |
344 | vf = get_response | |
345 | puts "curren vf is #{vf.inspect}" if $debug | |
346 | list << vf | |
347 | puts "current list is #{list.inspect}" if $debug | |
ed9b544e | 348 | end |
38210f7f | 349 | list |
350 | end | |
351 | ||
352 | def get_reply | |
353 | begin | |
354 | r = read(1) | |
355 | raise RedisError if (r == "\r" || r == "\n") | |
356 | rescue RedisError | |
357 | retry | |
ed9b544e | 358 | end |
38210f7f | 359 | r |
360 | end | |
361 | ||
362 | def status_code_reply | |
363 | begin | |
364 | res = read_proto | |
365 | if res.index('-') == 0 | |
366 | raise RedisError, res | |
367 | else | |
368 | true | |
369 | end | |
370 | rescue RedisError | |
371 | raise RedisError | |
372 | end | |
373 | end | |
374 | ||
375 | def execute_command(command, ignore_response=false) | |
376 | ss = server.socket | |
377 | unless ss.object_id == @socket.object_id | |
378 | @socket = ss | |
379 | puts "Socket changed, selecting DB" if $debug | |
380 | unless command[0..6] == 'SELECT' | |
381 | #BTM - Ugh- DRY but better than infinite recursion | |
382 | write("SELECT #{@db}\r\n") | |
383 | get_response | |
384 | end | |
385 | end | |
386 | write(command) | |
387 | get_response unless ignore_response | |
388 | rescue Errno::ECONNRESET, Errno::EPIPE, NoMethodError, Timeout::Error => e | |
389 | raise RedisError, "Connection error" | |
390 | end | |
69664139 | 391 | |
38210f7f | 392 | def get_response |
393 | rtype = get_reply | |
394 | puts "reply_type is #{rtype.inspect}" if $debug | |
395 | case rtype | |
396 | when SINGLE | |
397 | single_line | |
398 | when BULK | |
399 | bulk_reply | |
400 | when MULTI | |
401 | multi_bulk | |
402 | when INT | |
403 | integer_reply | |
404 | when ERR | |
405 | raise RedisError, single_line | |
406 | else | |
407 | raise RedisError, "Unknown response.." | |
408 | end | |
409 | end | |
410 | ||
411 | def integer_reply | |
412 | Integer(read_proto) | |
413 | end | |
414 | ||
415 | def single_line | |
416 | buff = "" | |
417 | while buff[-2..-1] != "\r\n" | |
418 | buff << read(1) | |
57172ffb | 419 | end |
38210f7f | 420 | puts "single_line value is #{buff[0..-3].inspect}" if $debug |
421 | buff[0..-3] | |
422 | end | |
423 | ||
424 | def read_socket | |
425 | begin | |
426 | socket = @server.socket | |
427 | while res = socket.read(8096) | |
428 | break if res.size != 8096 | |
429 | end | |
430 | #Timeout or server down | |
431 | rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED => e | |
432 | server.close | |
433 | puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug | |
434 | retry | |
435 | rescue Timeout::Error => e | |
436 | #BTM - Ignore this error so we don't go into an endless loop | |
437 | puts "Client (#{server.inspect}) Timeout\n" if $debug | |
438 | #Server down | |
439 | rescue NoMethodError => e | |
440 | puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug | |
441 | raise Errno::ECONNREFUSED | |
442 | #exit | |
443 | end | |
444 | end | |
445 | ||
446 | def read_proto | |
447 | res = @socket.readline | |
448 | x = res.chomp | |
449 | puts "read_proto is #{x.inspect}\n\n" if $debug | |
450 | x.to_i | |
451 | end | |
d7fc9edb | 452 | |
38210f7f | 453 | private |
454 | def value_to_wire(value) | |
455 | value_str = value.to_s | |
456 | if value_str.respond_to?(:bytesize) | |
457 | value_size = value_str.bytesize | |
458 | else | |
459 | value_size = value_str.size | |
ed9b544e | 460 | end |
38210f7f | 461 | "#{value_size}\r\n#{value_str}" |
462 | end | |
463 | ||
27dd1526 | 464 | end |