redis 0.1.2 → 0.2.0

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.
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