]> git.saurik.com Git - redis.git/blame - client-libraries/ruby_2/rubyredis.rb
__P completely removed from pqsort.c/h
[redis.git] / client-libraries / ruby_2 / rubyredis.rb
CommitLineData
4a327b4a 1# RubyRedis is an alternative implementatin of Ruby client library written
2# by Salvatore Sanfilippo.
3#
4# The aim of this library is to create an alternative client library that is
5# much simpler and does not implement every command explicitly but uses
6# method_missing instead.
7
8require 'socket'
e3d48807 9require 'set'
4a327b4a 10
0dd8fce1 11begin
06374c4b 12 if (RUBY_VERSION >= '1.9')
13 require 'timeout'
14 RedisTimer = Timeout
15 else
16 require 'system_timer'
17 RedisTimer = SystemTimer
18 end
0dd8fce1 19rescue LoadError
06374c4b 20 RedisTimer = nil
0dd8fce1 21end
22
4a327b4a 23class RedisClient
24 BulkCommands = {
25 "set"=>true, "setnx"=>true, "rpush"=>true, "lpush"=>true, "lset"=>true,
26 "lrem"=>true, "sadd"=>true, "srem"=>true, "sismember"=>true,
27 "echo"=>true, "getset"=>true, "smove"=>true
28 }
29
3ba37089 30 ConvertToBool = lambda{|r| r == 0 ? false : r}
31
32 ReplyProcessor = {
33 "exists" => ConvertToBool,
34 "sismember"=> ConvertToBool,
35 "sadd"=> ConvertToBool,
36 "srem"=> ConvertToBool,
37 "smove"=> ConvertToBool,
38 "move"=> ConvertToBool,
39 "setnx"=> ConvertToBool,
40 "del"=> ConvertToBool,
41 "renamenx"=> ConvertToBool,
42 "expire"=> ConvertToBool,
43 "keys" => lambda{|r| r.split(" ")},
44 "info" => lambda{|r|
45 info = {}
46 r.each_line {|kv|
f5bf7e3e 47 k,v = kv.split(":",2).map{|x| x.chomp}
3ba37089 48 info[k.to_sym] = v
49 }
50 info
51 }
52 }
53
0dd8fce1 54 Aliases = {
55 "flush_db" => "flushdb",
56 "flush_all" => "flushall",
57 "last_save" => "lastsave",
58 "key?" => "exists",
59 "delete" => "del",
60 "randkey" => "randomkey",
61 "list_length" => "llen",
0dd8fce1 62 "push_tail" => "rpush",
63 "push_head" => "lpush",
64 "pop_tail" => "rpop",
65 "pop_head" => "lpop",
66 "list_set" => "lset",
67 "list_range" => "lrange",
68 "list_trim" => "ltrim",
69 "list_index" => "lindex",
70 "list_rm" => "lrem",
71 "set_add" => "sadd",
72 "set_delete" => "srem",
73 "set_count" => "scard",
74 "set_member?" => "sismember",
75 "set_members" => "smembers",
76 "set_intersect" => "sinter",
e3d48807 77 "set_intersect_store" => "sinterstore",
0dd8fce1 78 "set_inter_store" => "sinterstore",
79 "set_union" => "sunion",
80 "set_union_store" => "sunionstore",
81 "set_diff" => "sdiff",
82 "set_diff_store" => "sdiffstore",
83 "set_move" => "smove",
e3d48807 84 "set_unless_exists" => "setnx",
85 "rename_unless_exists" => "renamenx"
0dd8fce1 86 }
87
4a327b4a 88 def initialize(opts={})
0dd8fce1 89 @host = opts[:host] || '127.0.0.1'
90 @port = opts[:port] || 6379
91 @db = opts[:db] || 0
92 @timeout = opts[:timeout] || 0
3f32f1f6 93 connect_to_server
4a327b4a 94 end
95
96 def to_s
97 "Redis Client connected to #{@host}:#{@port} against DB #{@db}"
98 end
99
100 def connect_to_server
0dd8fce1 101 @sock = connect_to(@host,@port,@timeout == 0 ? nil : @timeout)
3f32f1f6 102 call_command(["select",@db]) if @db != 0
4a327b4a 103 end
104
0dd8fce1 105 def connect_to(host, port, timeout=nil)
106 # We support connect() timeout only if system_timer is availabe
107 # or if we are running against Ruby >= 1.9
108 # Timeout reading from the socket instead will be supported anyway.
109 if @timeout != 0 and RedisTimer
110 begin
111 sock = TCPSocket.new(host, port, 0)
112 rescue Timeout::Error
e3d48807 113 @sock = nil
0dd8fce1 114 raise Timeout::Error, "Timeout connecting to the server"
115 end
116 else
117 sock = TCPSocket.new(host, port, 0)
118 end
a56785f7 119 sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
0dd8fce1 120
121 # If the timeout is set we set the low level socket options in order
122 # to make sure a blocking read will return after the specified number
123 # of seconds. This hack is from memcached ruby client.
124 if timeout
125 secs = Integer(timeout)
126 usecs = Integer((timeout - secs) * 1_000_000)
127 optval = [secs, usecs].pack("l_2")
128 sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
129 sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
130 end
131 sock
132 end
133
4a327b4a 134 def method_missing(*argv)
135 call_command(argv)
136 end
137
138 def call_command(argv)
3f32f1f6 139 # this wrapper to raw_call_command handle reconnection on socket
140 # error. We try to reconnect just one time, otherwise let the error
141 # araise.
0dd8fce1 142 connect_to_server if !@sock
3f32f1f6 143 begin
144 raw_call_command(argv)
145 rescue Errno::ECONNRESET
146 @sock.close
147 connect_to_server
148 raw_call_command(argv)
149 end
150 end
151
152 def raw_call_command(argv)
4a327b4a 153 bulk = nil
154 argv[0] = argv[0].to_s.downcase
0dd8fce1 155 argv[0] = Aliases[argv[0]] if Aliases[argv[0]]
0b420168 156 if BulkCommands[argv[0]] and argv.length > 1
3ba37089 157 bulk = argv[-1].to_s
4a327b4a 158 argv[-1] = bulk.length
159 end
160 @sock.write(argv.join(" ")+"\r\n")
161 @sock.write(bulk+"\r\n") if bulk
3ba37089 162
163 # Post process the reply if needed
164 processor = ReplyProcessor[argv[0]]
165 processor ? processor.call(read_reply) : read_reply
4a327b4a 166 end
167
4e1684df 168 def select(*args)
169 raise "SELECT not allowed, use the :db option when creating the object"
170 end
171
ad0ea27c 172 def [](key)
173 get(key)
174 end
175
176 def []=(key,value)
177 set(key,value)
178 end
179
0dd8fce1 180 def sort(key, opts={})
181 cmd = []
182 cmd << "SORT #{key}"
183 cmd << "BY #{opts[:by]}" if opts[:by]
184 cmd << "GET #{[opts[:get]].flatten * ' GET '}" if opts[:get]
185 cmd << "#{opts[:order]}" if opts[:order]
186 cmd << "LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
187 call_command(cmd)
188 end
189
190 def incr(key,increment=nil)
191 call_command(increment ? ["incrby",key,increment] : ["incr",key])
192 end
193
194 def decr(key,decrement=nil)
195 call_command(decrement ? ["decrby",key,decrement] : ["decr",key])
196 end
197
4a327b4a 198 def read_reply
0dd8fce1 199 # We read the first byte using read() mainly because gets() is
200 # immune to raw socket timeouts.
201 begin
202 rtype = @sock.read(1)
203 rescue Errno::EAGAIN
204 # We want to make sure it reconnects on the next command after the
205 # timeout. Otherwise the server may reply in the meantime leaving
206 # the protocol in a desync status.
207 @sock = nil
208 raise Errno::EAGAIN, "Timeout reading from the socket"
209 end
210
211 raise Errno::ECONNRESET,"Connection lost" if !rtype
4a327b4a 212 line = @sock.gets
0dd8fce1 213 case rtype
4a327b4a 214 when "-"
0dd8fce1 215 raise "-"+line.strip
4a327b4a 216 when "+"
0dd8fce1 217 line.strip
4a327b4a 218 when ":"
0dd8fce1 219 line.to_i
4a327b4a 220 when "$"
0dd8fce1 221 bulklen = line.to_i
4a327b4a 222 return nil if bulklen == -1
223 data = @sock.read(bulklen)
224 @sock.read(2) # CRLF
225 data
226 when "*"
0dd8fce1 227 objects = line.to_i
4a327b4a 228 return nil if bulklen == -1
229 res = []
230 objects.times {
231 res << read_reply
232 }
233 res
0dd8fce1 234 else
03fd01c7 235 raise "Protocol error, got '#{rtype}' as initial reply byte"
4a327b4a 236 end
237 end
238end