redis 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/redis.rb CHANGED
@@ -1,373 +1,23 @@
1
1
  require 'socket'
2
- require File.join(File.dirname(__FILE__),'pipeline')
2
+
3
+ module RedisRb
4
+ VERSION = "0.2.0"
5
+ end
3
6
 
4
7
  begin
5
8
  if RUBY_VERSION >= '1.9'
6
9
  require 'timeout'
7
- RedisTimer = Timeout
10
+ RedisRb::RedisTimer = Timeout
8
11
  else
9
12
  require 'system_timer'
10
- RedisTimer = SystemTimer
13
+ RedisRb::RedisTimer = SystemTimer
11
14
  end
12
15
  rescue LoadError
13
- RedisTimer = nil
16
+ RedisRb::RedisTimer = nil
14
17
  end
15
18
 
16
- class Redis
17
- OK = "OK".freeze
18
- MINUS = "-".freeze
19
- PLUS = "+".freeze
20
- COLON = ":".freeze
21
- DOLLAR = "$".freeze
22
- ASTERISK = "*".freeze
23
-
24
- BULK_COMMANDS = {
25
- "set" => true,
26
- "setnx" => true,
27
- "rpush" => true,
28
- "lpush" => true,
29
- "lset" => true,
30
- "lrem" => true,
31
- "sadd" => true,
32
- "srem" => true,
33
- "sismember" => true,
34
- "echo" => true,
35
- "getset" => true,
36
- "smove" => true,
37
- "zadd" => true,
38
- "zincrby" => true,
39
- "zrem" => true,
40
- "zscore" => true
41
- }
42
-
43
- MULTI_BULK_COMMANDS = {
44
- "mset" => true,
45
- "msetnx" => true
46
- }
47
-
48
- BOOLEAN_PROCESSOR = lambda{|r| r == 1 }
49
-
50
- REPLY_PROCESSOR = {
51
- "exists" => BOOLEAN_PROCESSOR,
52
- "sismember" => BOOLEAN_PROCESSOR,
53
- "sadd" => BOOLEAN_PROCESSOR,
54
- "srem" => BOOLEAN_PROCESSOR,
55
- "smove" => BOOLEAN_PROCESSOR,
56
- "zadd" => BOOLEAN_PROCESSOR,
57
- "zrem" => BOOLEAN_PROCESSOR,
58
- "move" => BOOLEAN_PROCESSOR,
59
- "setnx" => BOOLEAN_PROCESSOR,
60
- "del" => BOOLEAN_PROCESSOR,
61
- "renamenx" => BOOLEAN_PROCESSOR,
62
- "expire" => BOOLEAN_PROCESSOR,
63
- "keys" => lambda{|r| r.split(" ")},
64
- "info" => lambda{|r|
65
- info = {}
66
- r.each_line {|kv|
67
- k,v = kv.split(":",2).map{|x| x.chomp}
68
- info[k.to_sym] = v
69
- }
70
- info
71
- }
72
- }
73
-
74
- ALIASES = {
75
- "flush_db" => "flushdb",
76
- "flush_all" => "flushall",
77
- "last_save" => "lastsave",
78
- "key?" => "exists",
79
- "delete" => "del",
80
- "randkey" => "randomkey",
81
- "list_length" => "llen",
82
- "push_tail" => "rpush",
83
- "push_head" => "lpush",
84
- "pop_tail" => "rpop",
85
- "pop_head" => "lpop",
86
- "list_set" => "lset",
87
- "list_range" => "lrange",
88
- "list_trim" => "ltrim",
89
- "list_index" => "lindex",
90
- "list_rm" => "lrem",
91
- "set_add" => "sadd",
92
- "set_delete" => "srem",
93
- "set_count" => "scard",
94
- "set_member?" => "sismember",
95
- "set_members" => "smembers",
96
- "set_intersect" => "sinter",
97
- "set_intersect_store" => "sinterstore",
98
- "set_inter_store" => "sinterstore",
99
- "set_union" => "sunion",
100
- "set_union_store" => "sunionstore",
101
- "set_diff" => "sdiff",
102
- "set_diff_store" => "sdiffstore",
103
- "set_move" => "smove",
104
- "set_unless_exists" => "setnx",
105
- "rename_unless_exists" => "renamenx",
106
- "type?" => "type",
107
- "zset_add" => "zadd",
108
- "zset_count" => "zcard",
109
- "zset_range_by_score" => "zrangebyscore",
110
- "zset_reverse_range" => "zrevrange",
111
- "zset_range" => "zrange",
112
- "zset_delete" => "zrem",
113
- "zset_score" => "zscore",
114
- "zset_incr_by" => "zincrby",
115
- "zset_increment_by" => "zincrby"
116
- }
117
-
118
- DISABLED_COMMANDS = {
119
- "monitor" => true,
120
- "sync" => true
121
- }
122
-
123
- def initialize(options = {})
124
- @host = options[:host] || '127.0.0.1'
125
- @port = (options[:port] || 6379).to_i
126
- @db = (options[:db] || 0).to_i
127
- @timeout = (options[:timeout] || 5).to_i
128
- @password = options[:password]
129
- @logger = options[:logger]
130
- @thread_safe = options[:thread_safe]
131
- @mutex = Mutex.new if @thread_safe
132
- @sock = nil
133
-
134
- @logger.info { self.to_s } if @logger
135
- end
136
-
137
- def to_s
138
- "Redis Client connected to #{server} against DB #{@db}"
139
- end
140
-
141
- def server
142
- "#{@host}:#{@port}"
143
- end
144
-
145
- def connect_to_server
146
- @sock = connect_to(@host, @port, @timeout == 0 ? nil : @timeout)
147
- call_command(["auth",@password]) if @password
148
- call_command(["select",@db]) unless @db == 0
149
- end
150
-
151
- def connect_to(host, port, timeout=nil)
152
- # We support connect() timeout only if system_timer is availabe
153
- # or if we are running against Ruby >= 1.9
154
- # Timeout reading from the socket instead will be supported anyway.
155
- if @timeout != 0 and RedisTimer
156
- begin
157
- sock = TCPSocket.new(host, port)
158
- rescue Timeout::Error
159
- @sock = nil
160
- raise Timeout::Error, "Timeout connecting to the server"
161
- end
162
- else
163
- sock = TCPSocket.new(host, port)
164
- end
165
- sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
166
-
167
- # If the timeout is set we set the low level socket options in order
168
- # to make sure a blocking read will return after the specified number
169
- # of seconds. This hack is from memcached ruby client.
170
- if timeout
171
- secs = Integer(timeout)
172
- usecs = Integer((timeout - secs) * 1_000_000)
173
- optval = [secs, usecs].pack("l_2")
174
- begin
175
- sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
176
- sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
177
- rescue Exception => ex
178
- # Solaris, for one, does not like/support socket timeouts.
179
- @logger.info "Unable to use raw socket timeouts: #{ex.class.name}: #{ex.message}" if @logger
180
- end
181
- end
182
- sock
183
- end
184
-
185
- def method_missing(*argv)
186
- call_command(argv)
187
- end
188
-
189
- def call_command(argv)
190
- @logger.debug { argv.inspect } if @logger
191
-
192
- # this wrapper to raw_call_command handle reconnection on socket
193
- # error. We try to reconnect just one time, otherwise let the error
194
- # araise.
195
- connect_to_server if !@sock
196
-
197
- begin
198
- raw_call_command(argv.dup)
199
- rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED
200
- @sock.close rescue nil
201
- @sock = nil
202
- connect_to_server
203
- raw_call_command(argv.dup)
204
- end
205
- end
206
-
207
- def raw_call_command(argvp)
208
- pipeline = argvp[0].is_a?(Array)
209
-
210
- unless pipeline
211
- argvv = [argvp]
212
- else
213
- argvv = argvp
214
- end
215
-
216
- if MULTI_BULK_COMMANDS[argvv.flatten[0].to_s]
217
- # TODO improve this code
218
- argvp = argvv.flatten
219
- values = argvp.pop.to_a.flatten
220
- argvp = values.unshift(argvp[0])
221
- command = ["*#{argvp.size}"]
222
- argvp.each do |v|
223
- v = v.to_s
224
- command << "$#{get_size(v)}"
225
- command << v
226
- end
227
- command = command.map {|cmd| "#{cmd}\r\n"}.join
228
- else
229
- command = ""
230
- argvv.each do |argv|
231
- bulk = nil
232
- argv[0] = argv[0].to_s.downcase
233
- argv[0] = ALIASES[argv[0]] if ALIASES[argv[0]]
234
- raise "#{argv[0]} command is disabled" if DISABLED_COMMANDS[argv[0]]
235
- if BULK_COMMANDS[argv[0]] and argv.length > 1
236
- bulk = argv[-1].to_s
237
- argv[-1] = get_size(bulk)
238
- end
239
- command << "#{argv.join(' ')}\r\n"
240
- command << "#{bulk}\r\n" if bulk
241
- end
242
- end
243
- results = maybe_lock { process_command(command, argvv) }
244
-
245
- return pipeline ? results : results[0]
246
- end
247
-
248
- def process_command(command, argvv)
249
- @sock.write(command)
250
- argvv.map do |argv|
251
- processor = REPLY_PROCESSOR[argv[0]]
252
- processor ? processor.call(read_reply) : read_reply
253
- end
254
- end
255
-
256
- def maybe_lock(&block)
257
- if @thread_safe
258
- @mutex.synchronize &block
259
- else
260
- block.call
261
- end
262
- end
263
-
264
- def select(*args)
265
- raise "SELECT not allowed, use the :db option when creating the object"
266
- end
267
-
268
- def [](key)
269
- self.get(key)
270
- end
271
-
272
- def []=(key,value)
273
- set(key,value)
274
- end
275
-
276
- def set(key, value, expiry=nil)
277
- s = call_command([:set, key, value]) == OK
278
- expire(key, expiry) if s && expiry
279
- s
280
- end
281
-
282
- def sort(key, options = {})
283
- cmd = ["SORT"]
284
- cmd << key
285
- cmd << "BY #{options[:by]}" if options[:by]
286
- cmd << "GET #{[options[:get]].flatten * ' GET '}" if options[:get]
287
- cmd << "#{options[:order]}" if options[:order]
288
- cmd << "LIMIT #{options[:limit].join(' ')}" if options[:limit]
289
- call_command(cmd)
290
- end
291
-
292
- def incr(key, increment = nil)
293
- call_command(increment ? ["incrby",key,increment] : ["incr",key])
294
- end
19
+ require 'redis/client'
20
+ require 'redis/pipeline'
295
21
 
296
- def decr(key,decrement = nil)
297
- call_command(decrement ? ["decrby",key,decrement] : ["decr",key])
298
- end
299
-
300
- # Similar to memcache.rb's #get_multi, returns a hash mapping
301
- # keys to values.
302
- def mapped_mget(*keys)
303
- result = {}
304
- mget(*keys).each do |value|
305
- key = keys.shift
306
- result.merge!(key => value) unless value.nil?
307
- end
308
- result
309
- end
310
-
311
- # Ruby defines a now deprecated type method so we need to override it here
312
- # since it will never hit method_missing
313
- def type(key)
314
- call_command(['type', key])
315
- end
316
-
317
- def quit
318
- call_command(['quit'])
319
- rescue Errno::ECONNRESET
320
- end
321
-
322
- def pipelined(&block)
323
- pipeline = Pipeline.new self
324
- yield pipeline
325
- pipeline.execute
326
- end
327
-
328
- def read_reply
329
- # We read the first byte using read() mainly because gets() is
330
- # immune to raw socket timeouts.
331
- begin
332
- rtype = @sock.read(1)
333
- rescue Errno::EAGAIN
334
- # We want to make sure it reconnects on the next command after the
335
- # timeout. Otherwise the server may reply in the meantime leaving
336
- # the protocol in a desync status.
337
- @sock = nil
338
- raise Errno::EAGAIN, "Timeout reading from the socket"
339
- end
340
-
341
- raise Errno::ECONNRESET,"Connection lost" if !rtype
342
- line = @sock.gets
343
- case rtype
344
- when MINUS
345
- raise MINUS + line.strip
346
- when PLUS
347
- line.strip
348
- when COLON
349
- line.to_i
350
- when DOLLAR
351
- bulklen = line.to_i
352
- return nil if bulklen == -1
353
- data = @sock.read(bulklen)
354
- @sock.read(2) # CRLF
355
- data
356
- when ASTERISK
357
- objects = line.to_i
358
- return nil if bulklen == -1
359
- res = []
360
- objects.times {
361
- res << read_reply
362
- }
363
- res
364
- else
365
- raise "Protocol error, got '#{rtype}' as initial reply byte"
366
- end
367
- end
368
-
369
- private
370
- def get_size(string)
371
- string.respond_to?(:bytesize) ? string.bytesize : string.size
372
- end
373
- end
22
+ # For backwards compatibility
23
+ Redis = RedisRb::Client
data/spec/redis_spec.rb CHANGED
@@ -430,6 +430,15 @@ describe "redis" do
430
430
  @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1]).should == ['louie', 'mutt']
431
431
  @r.sort('dogs', :get => ['dog:*:name', 'dog:*:breed'], :limit => [0,1], :order => 'desc alpha').should == ['taj', 'terrier']
432
432
  end
433
+
434
+ it "should be able to store a SORT result" do
435
+ @r.rpush 'colors', 'red'
436
+ @r.rpush 'colors', 'yellow'
437
+ @r.rpush 'colors', 'orange'
438
+ @r.rpush 'colors', 'green'
439
+ @r.sort('colors', :limit => [1,2], :order => 'desc alpha', :store => 'sorted.colors')
440
+ @r.lrange('sorted.colors', 0, -1).should == ['red', 'orange']
441
+ end
433
442
  #
434
443
  it "should be able count the members of a zset" do
435
444
  @r.set_add "set", 'key1'
@@ -439,7 +448,7 @@ describe "redis" do
439
448
  @r.delete('set')
440
449
  @r.delete('zset')
441
450
  end
442
- #
451
+ #
443
452
  it "should be able add members to a zset" do
444
453
  @r.set_add "set", 'key1'
445
454
  @r.set_add "set", 'key2'
@@ -449,7 +458,7 @@ describe "redis" do
449
458
  @r.delete('set')
450
459
  @r.delete('zset')
451
460
  end
452
- #
461
+ #
453
462
  it "should be able delete members to a zset" do
454
463
  @r.set_add "set", 'key1'
455
464
  @r.set_add "set", 'key2'
@@ -467,7 +476,7 @@ describe "redis" do
467
476
  @r.delete('set2')
468
477
  @r.delete('zset')
469
478
  end
470
- #
479
+ #
471
480
  it "should be able to get a range of values from a zset" do
472
481
  @r.set_add "set", 'key1'
473
482
  @r.set_add "set", 'key2'
@@ -487,7 +496,7 @@ describe "redis" do
487
496
  @r.delete('set3')
488
497
  @r.delete('zset')
489
498
  end
490
- #
499
+ #
491
500
  it "should be able to get a reverse range of values from a zset" do
492
501
  @r.set_add "set", 'key1'
493
502
  @r.set_add "set", 'key2'
@@ -507,7 +516,7 @@ describe "redis" do
507
516
  @r.delete('set3')
508
517
  @r.delete('zset')
509
518
  end
510
- #
519
+ #
511
520
  it "should be able to get a range by score of values from a zset" do
512
521
  @r.set_add "set", 'key1'
513
522
  @r.set_add "set", 'key2'
@@ -630,7 +639,7 @@ describe "redis" do
630
639
  end
631
640
 
632
641
  it "should handle multiple servers" do
633
- require 'dist_redis'
642
+ require 'redis/dist_redis'
634
643
  @r = DistRedis.new(:hosts=> ['localhost:6379', '127.0.0.1:6379'], :db => 15)
635
644
 
636
645
  100.times do |idx|
@@ -640,6 +649,8 @@ describe "redis" do
640
649
  100.times do |idx|
641
650
  @r[idx].should == "foo#{idx}"
642
651
  end
652
+
653
+ @r.keys('*').sort.first.should == "0"
643
654
  end
644
655
 
645
656
  it "should be able to pipeline writes" do
@@ -665,4 +676,77 @@ describe "redis" do
665
676
  r.connect_to_server
666
677
  end
667
678
 
679
+ it "should run MULTI without a block" do
680
+ @r.multi
681
+ @r.get("key1").should == "QUEUED"
682
+ @r.discard
683
+ end
684
+
685
+ it "should run MULTI/EXEC with a block" do
686
+ @r.multi do
687
+ @r.set "key1", "value1"
688
+ end
689
+
690
+ @r.get("key1").should == "value1"
691
+
692
+ begin
693
+ @r.multi do
694
+ @r.set "key2", "value2"
695
+ raise "Some error"
696
+ @r.set "key3", "value3"
697
+ end
698
+ rescue
699
+ end
700
+
701
+ @r.get("key2").should be_nil
702
+ @r.get("key3").should be_nil
703
+ end
704
+
705
+ it "should yield the Redis object when using #multi with a block" do
706
+ @r.multi do |multi|
707
+ multi.set "key1", "value1"
708
+ end
709
+
710
+ @r.get("key1").should == "value1"
711
+ end
712
+
713
+ it "can set and get hash values" do
714
+ @r.hset("rush", "signals", "1982").should be_true
715
+ @r.hexists("rush", "signals").should be_true
716
+ @r.hget("rush", "signals").should == "1982"
717
+ end
718
+
719
+ it "can delete hash values" do
720
+ @r.hset("rush", "YYZ", "1981")
721
+ @r.hdel("rush", "YYZ").should be_true
722
+ @r.hexists("rush", "YYZ").should be_false
723
+ end
724
+
725
+ describe "with some hash values" do
726
+ before(:each) do
727
+ @r.hset("rush", "permanent waves", "1980")
728
+ @r.hset("rush", "moving pictures", "1981")
729
+ @r.hset("rush", "signals", "1982")
730
+ end
731
+
732
+ it "can get the length of the hash" do
733
+ @r.hlen("rush").should == 3
734
+ @r.hlen("yyz").should be_zero
735
+ end
736
+
737
+ it "can get the keys and values of the hash" do
738
+ @r.hkeys("rush").should == ["permanent waves", "moving pictures", "signals"]
739
+ @r.hvals("rush").should == %w[1980 1981 1982]
740
+ @r.hvals("yyz").should be_empty
741
+ end
742
+
743
+ it "returns a hash for HGETALL" do
744
+ @r.hgetall("rush").should == {
745
+ "permanent waves" => "1980",
746
+ "moving pictures" => "1981",
747
+ "signals" => "1982"
748
+ }
749
+ @r.hgetall("yyz").should == {}
750
+ end
751
+ end
668
752
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Ezra Zygmuntowicz
@@ -14,19 +19,21 @@ autorequire: redis
14
19
  bindir: bin
15
20
  cert_chain: []
16
21
 
17
- date: 2010-01-27 00:00:00 -08:00
22
+ date: 2010-03-23 00:00:00 -07:00
18
23
  default_executable:
19
24
  dependencies:
20
25
  - !ruby/object:Gem::Dependency
21
26
  name: rspec
22
- type: :development
23
- version_requirement:
24
- version_requirements: !ruby/object:Gem::Requirement
27
+ prerelease: false
28
+ requirement: &id001 !ruby/object:Gem::Requirement
25
29
  requirements:
26
30
  - - ">="
27
31
  - !ruby/object:Gem::Version
32
+ segments:
33
+ - 0
28
34
  version: "0"
29
- version:
35
+ type: :development
36
+ version_requirements: *id001
30
37
  description: Ruby client library for redis key value storage server
31
38
  email: ez@engineyard.com
32
39
  executables: []
@@ -39,9 +46,11 @@ files:
39
46
  - LICENSE
40
47
  - README.markdown
41
48
  - Rakefile
42
- - lib/dist_redis.rb
43
- - lib/hash_ring.rb
44
- - lib/pipeline.rb
49
+ - lib/edis.rb
50
+ - lib/redis/client.rb
51
+ - lib/redis/dist_redis.rb
52
+ - lib/redis/hash_ring.rb
53
+ - lib/redis/pipeline.rb
45
54
  - lib/redis/raketasks.rb
46
55
  - lib/redis.rb
47
56
  - tasks/redis.tasks.rb
@@ -60,18 +69,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
60
69
  requirements:
61
70
  - - ">="
62
71
  - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
63
74
  version: "0"
64
- version:
65
75
  required_rubygems_version: !ruby/object:Gem::Requirement
66
76
  requirements:
67
77
  - - ">="
68
78
  - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
69
81
  version: "0"
70
- version:
71
82
  requirements: []
72
83
 
73
84
  rubyforge_project:
74
- rubygems_version: 1.3.5
85
+ rubygems_version: 1.3.6
75
86
  signing_key:
76
87
  specification_version: 3
77
88
  summary: Ruby client library for redis key value storage server