timcharper-redis 0.0.3.4

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/server.rb ADDED
@@ -0,0 +1,128 @@
1
+ ##
2
+ # This class represents a redis server instance.
3
+
4
+ class Server
5
+
6
+ ##
7
+ # The amount of time to wait before attempting to re-establish a
8
+ # connection with a server that is marked dead.
9
+
10
+ RETRY_DELAY = 30.0
11
+
12
+ ##
13
+ # The host the redis server is running on.
14
+
15
+ attr_reader :host
16
+
17
+ ##
18
+ # The port the redis server is listening on.
19
+
20
+ attr_reader :port
21
+
22
+ ##
23
+ #
24
+
25
+ attr_reader :replica
26
+
27
+ ##
28
+ # The time of next retry if the connection is dead.
29
+
30
+ attr_reader :retry
31
+
32
+ ##
33
+ # A text status string describing the state of the server.
34
+
35
+ attr_reader :status
36
+
37
+ ##
38
+ # Create a new Redis::Server object for the redis instance
39
+ # listening on the given host and port.
40
+
41
+ def initialize(host, port = DEFAULT_PORT, timeout = 10)
42
+ raise ArgumentError, "No host specified" if host.nil? or host.empty?
43
+ raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
44
+
45
+ @host = host
46
+ @port = port.to_i
47
+
48
+ @sock = nil
49
+ @retry = nil
50
+ @status = 'NOT CONNECTED'
51
+ @timeout = timeout
52
+ end
53
+
54
+ ##
55
+ # Return a string representation of the server object.
56
+ def inspect
57
+ "<Redis::Server: %s:%d (%s)>" % [@host, @port, @status]
58
+ end
59
+
60
+ ##
61
+ # Try to connect to the redis server targeted by this object.
62
+ # Returns the connected socket object on success or nil on failure.
63
+
64
+ def socket
65
+ return @sock if @sock and not @sock.closed?
66
+
67
+ @sock = nil
68
+
69
+ # If the host was dead, don't retry for a while.
70
+ return if @retry and @retry > Time.now
71
+
72
+ # Attempt to connect if not already connected.
73
+ begin
74
+ @sock = connect_to(@host, @port, @timeout)
75
+ @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
76
+ @retry = nil
77
+ @status = 'CONNECTED'
78
+ rescue Errno::EPIPE, Errno::ECONNREFUSED => e
79
+ puts "Socket died... socket: #{@sock.inspect}\n" if $debug
80
+ @sock.close
81
+ retry
82
+ rescue SocketError, SystemCallError, IOError => err
83
+ puts "Unable to open socket: #{err.class.name}, #{err.message}" if $debug
84
+ mark_dead err
85
+ end
86
+
87
+ return @sock
88
+ end
89
+
90
+ def connect_to(host, port, timeout=nil)
91
+ addrs = Socket.getaddrinfo(host, nil)
92
+ addr = addrs.detect { |ad| ad[0] == 'AF_INET' }
93
+ sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
94
+ if timeout
95
+ secs = Integer(timeout)
96
+ usecs = Integer((timeout - secs) * 1_000_000)
97
+ optval = [secs, usecs].pack("l_2")
98
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
99
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
100
+ end
101
+ sock.connect(Socket.pack_sockaddr_in(port, addr[3]))
102
+ sock
103
+ end
104
+
105
+ ##
106
+ # Close the connection to the redis server targeted by this
107
+ # object. The server is not considered dead.
108
+
109
+ def close
110
+ @sock.close if @sock && !@sock.closed?
111
+ @sock = nil
112
+ @retry = nil
113
+ @status = "NOT CONNECTED"
114
+ end
115
+
116
+ ##
117
+ # Mark the server as dead and close its socket.
118
+ def mark_dead(error)
119
+ @sock.close if @sock && !@sock.closed?
120
+ @sock = nil
121
+ @retry = Time.now #+ RETRY_DELAY
122
+
123
+ reason = "#{error.class.name}: #{error.message}"
124
+ @status = sprintf "%s:%s DEAD (%s), will retry at %s", @host, @port, reason, @retry
125
+ puts @status
126
+ end
127
+
128
+ end
@@ -0,0 +1,401 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class Foo
4
+ attr_accessor :bar
5
+ def initialize(bar)
6
+ @bar = bar
7
+ end
8
+
9
+ def ==(other)
10
+ @bar == other.bar
11
+ end
12
+ end
13
+
14
+ describe "redis" do
15
+ before(:all) do
16
+ @r = Redis.new
17
+ @r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data
18
+ end
19
+
20
+ before(:each) do
21
+ @r['foo'] = 'bar'
22
+ end
23
+
24
+ after(:each) do
25
+ @r.keys('*').each {|k| @r.delete k}
26
+ end
27
+
28
+ after(:all) do
29
+ @r.quit
30
+ end
31
+
32
+
33
+ it "should be able to GET a key" do
34
+ @r['foo'].should == 'bar'
35
+ end
36
+
37
+ it "should be able to SET a key" do
38
+ @r['foo'] = 'nik'
39
+ @r['foo'].should == 'nik'
40
+ end
41
+
42
+ it "should properly handle trailing newline characters" do
43
+ @r['foo'] = "bar\n"
44
+ @r['foo'].should == "bar\n"
45
+ end
46
+
47
+ it "should store and retrieve all possible characters at the beginning and the end of a string" do
48
+ (0..255).each do |char_idx|
49
+ string = "#{char_idx.chr}---#{char_idx.chr}"
50
+ @r['foo'] = string
51
+ @r['foo'].should == string
52
+ end
53
+ end
54
+
55
+ it "should be able to SET a key with an expiry" do
56
+ @r.set('foo', 'bar', 1)
57
+ @r['foo'].should == 'bar'
58
+ sleep 2
59
+ @r['foo'].should == nil
60
+ end
61
+
62
+ it "should be able to SETNX(set_unless_exists)" do
63
+ @r['foo'] = 'nik'
64
+ @r['foo'].should == 'nik'
65
+ @r.set_unless_exists 'foo', 'bar'
66
+ @r['foo'].should == 'nik'
67
+ end
68
+ #
69
+ it "should be able to INCR(increment) a key" do
70
+ @r.delete('counter')
71
+ @r.incr('counter').should == 1
72
+ @r.incr('counter').should == 2
73
+ @r.incr('counter').should == 3
74
+ end
75
+ #
76
+ it "should be able to DECR(decrement) a key" do
77
+ @r.delete('counter')
78
+ @r.incr('counter').should == 1
79
+ @r.incr('counter').should == 2
80
+ @r.incr('counter').should == 3
81
+ @r.decr('counter').should == 2
82
+ @r.decr('counter', 2).should == 0
83
+ end
84
+ #
85
+ it "should be able to RANDKEY(return a random key)" do
86
+ @r.randkey.should_not be_nil
87
+ end
88
+ #
89
+ it "should be able to RENAME a key" do
90
+ @r.delete 'foo'
91
+ @r.delete 'bar'
92
+ @r['foo'] = 'hi'
93
+ @r.rename! 'foo', 'bar'
94
+ @r['bar'].should == 'hi'
95
+ end
96
+ #
97
+ it "should be able to RENAMENX(rename unless the new key already exists) a key" do
98
+ @r.delete 'foo'
99
+ @r.delete 'bar'
100
+ @r['foo'] = 'hi'
101
+ @r['bar'] = 'ohai'
102
+ lambda {@r.rename 'foo', 'bar'}.should raise_error(RedisRenameError)
103
+ @r['bar'].should == 'ohai'
104
+ end
105
+ #
106
+ it "should be able to EXPIRE a key" do
107
+ @r['foo'] = 'bar'
108
+ @r.expire('foo', 1)
109
+ @r['foo'].should == "bar"
110
+ sleep 2
111
+ @r['foo'].should == nil
112
+ end
113
+ #
114
+ it "should be able to EXISTS(check if key exists)" do
115
+ @r['foo'] = 'nik'
116
+ @r.key?('foo').should be_true
117
+ @r.delete 'foo'
118
+ @r.key?('foo').should be_false
119
+ end
120
+ #
121
+ it "should be able to KEYS(glob for keys)" do
122
+ @r.keys("f*").each do |key|
123
+ @r.delete key
124
+ end
125
+ @r['f'] = 'nik'
126
+ @r['fo'] = 'nak'
127
+ @r['foo'] = 'qux'
128
+ @r.keys("f*").sort.should == ['f','fo', 'foo'].sort
129
+ end
130
+ #
131
+ it "should be able to check the TYPE of a key" do
132
+ @r['foo'] = 'nik'
133
+ @r.type?('foo').should == "string"
134
+ @r.delete 'foo'
135
+ @r.type?('foo').should == "none"
136
+ end
137
+ #
138
+ it "should be able to push to the head of a list" do
139
+ @r.push_head "list", 'hello'
140
+ @r.push_head "list", 42
141
+ @r.type?('list').should == "list"
142
+ @r.list_length('list').should == 2
143
+ @r.pop_head('list').should == '42'
144
+ @r.delete('list')
145
+ end
146
+ #
147
+ it "should be able to push to the tail of a list" do
148
+ @r.push_tail "list", 'hello'
149
+ @r.type?('list').should == "list"
150
+ @r.list_length('list').should == 1
151
+ @r.delete('list')
152
+ end
153
+ #
154
+ it "should be able to pop the tail of a list" do
155
+ @r.push_tail "list", 'hello'
156
+ @r.push_tail "list", 'goodbye'
157
+ @r.type?('list').should == "list"
158
+ @r.list_length('list').should == 2
159
+ @r.pop_tail('list').should == 'goodbye'
160
+ @r.delete('list')
161
+ end
162
+ #
163
+ it "should be able to pop the head of a list" do
164
+ @r.push_tail "list", 'hello'
165
+ @r.push_tail "list", 'goodbye'
166
+ @r.type?('list').should == "list"
167
+ @r.list_length('list').should == 2
168
+ @r.pop_head('list').should == 'hello'
169
+ @r.delete('list')
170
+ end
171
+ #
172
+ it "should be able to get the length of a list" do
173
+ @r.push_tail "list", 'hello'
174
+ @r.push_tail "list", 'goodbye'
175
+ @r.type?('list').should == "list"
176
+ @r.list_length('list').should == 2
177
+ @r.delete('list')
178
+ end
179
+ #
180
+ it "should be able to get a range of values from a list" do
181
+ @r.push_tail "list", 'hello'
182
+ @r.push_tail "list", 'goodbye'
183
+ @r.push_tail "list", '1'
184
+ @r.push_tail "list", '2'
185
+ @r.push_tail "list", '3'
186
+ @r.type?('list').should == "list"
187
+ @r.list_length('list').should == 5
188
+ @r.list_range('list', 2, -1).should == ['1', '2', '3']
189
+ @r.delete('list')
190
+ end
191
+ #
192
+ it "should be able to trim a list" do
193
+ @r.push_tail "list", 'hello'
194
+ @r.push_tail "list", 'goodbye'
195
+ @r.push_tail "list", '1'
196
+ @r.push_tail "list", '2'
197
+ @r.push_tail "list", '3'
198
+ @r.type?('list').should == "list"
199
+ @r.list_length('list').should == 5
200
+ @r.list_trim 'list', 0, 1
201
+ @r.list_length('list').should == 2
202
+ @r.list_range('list', 0, -1).should == ['hello', 'goodbye']
203
+ @r.delete('list')
204
+ end
205
+ #
206
+ it "should be able to get a value by indexing into a list" do
207
+ @r.push_tail "list", 'hello'
208
+ @r.push_tail "list", 'goodbye'
209
+ @r.type?('list').should == "list"
210
+ @r.list_length('list').should == 2
211
+ @r.list_index('list', 1).should == 'goodbye'
212
+ @r.delete('list')
213
+ end
214
+ #
215
+ it "should be able to set a value by indexing into a list" do
216
+ @r.push_tail "list", 'hello'
217
+ @r.push_tail "list", 'hello'
218
+ @r.type?('list').should == "list"
219
+ @r.list_length('list').should == 2
220
+ @r.list_set('list', 1, 'goodbye').should be_true
221
+ @r.list_index('list', 1).should == 'goodbye'
222
+ @r.delete('list')
223
+ end
224
+ #
225
+ it "should be able to remove values from a list LREM" do
226
+ @r.push_tail "list", 'hello'
227
+ @r.push_tail "list", 'goodbye'
228
+ @r.type?('list').should == "list"
229
+ @r.list_length('list').should == 2
230
+ @r.list_rm('list', 1, 'hello').should == 1
231
+ @r.list_range('list', 0, -1).should == ['goodbye']
232
+ @r.delete('list')
233
+ end
234
+ #
235
+ it "should be able add members to a set" do
236
+ @r.set_add "set", 'key1'
237
+ @r.set_add "set", 'key2'
238
+ @r.type?('set').should == "set"
239
+ @r.set_count('set').should == 2
240
+ @r.set_members('set').sort.should == ['key1', 'key2'].sort
241
+ @r.delete('set')
242
+ end
243
+ #
244
+ it "should be able delete members to a set" do
245
+ @r.set_add "set", 'key1'
246
+ @r.set_add "set", 'key2'
247
+ @r.type?('set').should == "set"
248
+ @r.set_count('set').should == 2
249
+ @r.set_members('set').should == Set.new(['key1', 'key2'])
250
+ @r.set_delete('set', 'key1')
251
+ @r.set_count('set').should == 1
252
+ @r.set_members('set').should == Set.new(['key2'])
253
+ @r.delete('set')
254
+ end
255
+ #
256
+ it "should be able count the members of a set" do
257
+ @r.set_add "set", 'key1'
258
+ @r.set_add "set", 'key2'
259
+ @r.type?('set').should == "set"
260
+ @r.set_count('set').should == 2
261
+ @r.delete('set')
262
+ end
263
+ #
264
+ it "should be able test for set membership" do
265
+ @r.set_add "set", 'key1'
266
+ @r.set_add "set", 'key2'
267
+ @r.type?('set').should == "set"
268
+ @r.set_count('set').should == 2
269
+ @r.set_member?('set', 'key1').should be_true
270
+ @r.set_member?('set', 'key2').should be_true
271
+ @r.set_member?('set', 'notthere').should be_false
272
+ @r.delete('set')
273
+ end
274
+ #
275
+ it "should be able to do set intersection" do
276
+ @r.set_add "set", 'key1'
277
+ @r.set_add "set", 'key2'
278
+ @r.set_add "set2", 'key2'
279
+ @r.set_intersect('set', 'set2').should == Set.new(['key2'])
280
+ @r.delete('set')
281
+ end
282
+ #
283
+ it "should be able to do set intersection and store the results in a key" do
284
+ @r.set_add "set", 'key1'
285
+ @r.set_add "set", 'key2'
286
+ @r.set_add "set2", 'key2'
287
+ count = @r.set_inter_store('newone', 'set', 'set2')
288
+ count.should == 1
289
+ @r.set_members('newone').should == Set.new(['key2'])
290
+ @r.delete('set')
291
+ end
292
+ #
293
+ it "should be able to do set union" do
294
+ @r.set_add "set", 'key1'
295
+ @r.set_add "set", 'key2'
296
+ @r.set_add "set2", 'key2'
297
+ @r.set_add "set2", 'key3'
298
+ @r.set_union('set', 'set2').should == Set.new(['key1','key2','key3'])
299
+ @r.delete('set')
300
+ end
301
+ #
302
+ it "should be able to do set union and store the results in a key" do
303
+ @r.set_add "set", 'key1'
304
+ @r.set_add "set", 'key2'
305
+ @r.set_add "set2", 'key2'
306
+ @r.set_add "set2", 'key3'
307
+ count = @r.set_union_store('newone', 'set', 'set2')
308
+ count.should == 3
309
+ @r.set_members('newone').should == Set.new(['key1','key2','key3'])
310
+ @r.delete('set')
311
+ end
312
+ #
313
+ it "should be able to do set difference" do
314
+ @r.set_add "set", 'key1'
315
+ @r.set_add "set", 'key2'
316
+ @r.set_add "set2", 'key2'
317
+ @r.set_add "set2", 'key3'
318
+ @r.set_diff('set', 'set2').should == Set.new(['key1','key3'])
319
+ @r.delete('set')
320
+ end
321
+ #
322
+ it "should be able to do set difference and store the results in a key" do
323
+ @r.set_add "set", 'key1'
324
+ @r.set_add "set", 'key2'
325
+ @r.set_add "set2", 'key2'
326
+ @r.set_add "set2", 'key3'
327
+ count = @r.set_diff_store('newone', 'set', 'set2')
328
+ count.should == 3
329
+ @r.set_members('newone').should == Set.new(['key1','key3'])
330
+ @r.delete('set')
331
+ end
332
+ it "should be able to do crazy SORT queries" do
333
+ @r['dog_1'] = 'louie'
334
+ @r.push_tail 'dogs', 1
335
+ @r['dog_2'] = 'lucy'
336
+ @r.push_tail 'dogs', 2
337
+ @r['dog_3'] = 'max'
338
+ @r.push_tail 'dogs', 3
339
+ @r['dog_4'] = 'taj'
340
+ @r.push_tail 'dogs', 4
341
+ @r.sort('dogs', :get => 'dog_*', :limit => [0,1]).should == ['louie']
342
+ @r.sort('dogs', :get => 'dog_*', :limit => [0,1], :order => 'desc alpha').should == ['taj']
343
+ end
344
+ #
345
+ it "should provide info" do
346
+ [:last_save_time, :redis_version, :total_connections_received, :connected_clients, :total_commands_processed, :connected_slaves, :uptime_in_seconds, :used_memory, :uptime_in_days, :changes_since_last_save].each do |x|
347
+ @r.info.keys.should include(x)
348
+ end
349
+ end
350
+ #
351
+ it "should be able to flush the database" do
352
+ @r['key1'] = 'keyone'
353
+ @r['key2'] = 'keytwo'
354
+ @r.keys('*').sort.should == ['foo', 'key1', 'key2'] #foo from before
355
+ @r.flush_db
356
+ @r.keys('*').should == []
357
+ end
358
+ #
359
+ it "should be able to provide the last save time" do
360
+ savetime = @r.last_save
361
+ Time.at(savetime).class.should == Time
362
+ Time.at(savetime).should <= Time.now
363
+ end
364
+
365
+ it "should be able to MGET keys" do
366
+ @r['foo'] = 1000
367
+ @r['bar'] = 2000
368
+ @r.mget('foo', 'bar').should == ['1000', '2000']
369
+ @r.mget('foo', 'bar', 'baz').should == ['1000', '2000', nil]
370
+ end
371
+
372
+ it "should bgsave" do
373
+ lambda {@r.bgsave}.should_not raise_error(RedisError)
374
+ end
375
+
376
+ it "should handle multiple servers" do
377
+ require 'dist_redis'
378
+ @r = DistRedis.new('localhost:6379', '127.0.0.1:6379')
379
+ @r.select_db(15) # use database 15 for testing so we dont accidentally step on you real data
380
+
381
+ 100.times do |idx|
382
+ @r[idx] = "foo#{idx}"
383
+ end
384
+
385
+ 100.times do |idx|
386
+ @r[idx].should == "foo#{idx}"
387
+ end
388
+ end
389
+
390
+ it "should be able to pipeline writes" do
391
+ @r.pipelined do |pipeline|
392
+ pipeline.push_head "list", "hello"
393
+ pipeline.push_head "list", 42
394
+ end
395
+
396
+ @r.type?('list').should == "list"
397
+ @r.list_length('list').should == 2
398
+ @r.pop_head('list').should == '42'
399
+ @r.delete('list')
400
+ end
401
+ end