seamless_database_pool 1.0.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/MIT-LICENSE +20 -0
- data/README +79 -0
- data/Rakefile +43 -0
- data/init.rb +2 -0
- data/lib/active_record/connection_adapters/seamless_database_pool_adapter.rb +302 -0
- data/lib/seamless_database_pool.rb +108 -0
- data/lib/seamless_database_pool/connect_timeout.rb +22 -0
- data/lib/seamless_database_pool/connection_statistics.rb +63 -0
- data/lib/seamless_database_pool/controller_filter.rb +91 -0
- data/spec/connection_statistics_spec.rb +77 -0
- data/spec/filter_spec.rb +134 -0
- data/spec/seamless_database_pool_adapter_spec.rb +432 -0
- data/spec/seamless_database_pool_spec.rb +134 -0
- metadata +72 -0
@@ -0,0 +1,432 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
module SeamlessDatabasePool
|
4
|
+
class MockConnection < ActiveRecord::ConnectionAdapters::AbstractAdapter
|
5
|
+
def initialize (name)
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def inspect
|
10
|
+
"#{@name} connection"
|
11
|
+
end
|
12
|
+
|
13
|
+
def reconnect!
|
14
|
+
sleep(0.25)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class MockMasterConnection < MockConnection
|
19
|
+
def insert (sql, name = nil); end
|
20
|
+
def update (sql, name = nil); end
|
21
|
+
def execute (sql, name = nil); end
|
22
|
+
def columns (table_name, name = nil); end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "SeamlessDatabasePoolAdapter ActiveRecord::Base extension" do
|
27
|
+
|
28
|
+
it "should establish the connections in the pool merging global options into the connection options" do
|
29
|
+
options = {
|
30
|
+
:adapter => 'seamless_database_pool',
|
31
|
+
:pool_adapter => 'reader',
|
32
|
+
:username => 'user',
|
33
|
+
:master => {
|
34
|
+
'adapter' => 'writer',
|
35
|
+
'host' => 'master_host'
|
36
|
+
},
|
37
|
+
:read_pool => [
|
38
|
+
{'host' => 'read_host_1'},
|
39
|
+
{'host' => 'read_host_2', 'pool_weight' => '2'},
|
40
|
+
{'host' => 'read_host_3', 'pool_weight' => '0'}
|
41
|
+
]
|
42
|
+
}
|
43
|
+
|
44
|
+
pool_connection = mock(:connection)
|
45
|
+
master_connection = SeamlessDatabasePool::MockConnection.new("master")
|
46
|
+
read_connection_1 = SeamlessDatabasePool::MockConnection.new("read_1")
|
47
|
+
read_connection_2 = SeamlessDatabasePool::MockConnection.new("read_2")
|
48
|
+
logger = ActiveRecord::Base.logger
|
49
|
+
weights = {master_connection => 1, read_connection_1 => 1, read_connection_2 => 2}
|
50
|
+
|
51
|
+
ActiveRecord::Base.should_receive(:writer_connection).with(:adapter => 'writer', :host => 'master_host', :username => 'user', :pool_weight => 1).and_return(master_connection)
|
52
|
+
ActiveRecord::Base.should_receive(:reader_connection).with(:adapter => 'reader', :host => 'read_host_1', :username => 'user', :pool_weight => 1).and_return(read_connection_1)
|
53
|
+
ActiveRecord::Base.should_receive(:reader_connection).with(:adapter => 'reader', :host => 'read_host_2', :username => 'user', :pool_weight => 2).and_return(read_connection_2)
|
54
|
+
|
55
|
+
ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.should_receive(:new).with(nil, logger, master_connection, [read_connection_1, read_connection_2], weights).and_return(pool_connection)
|
56
|
+
|
57
|
+
ActiveRecord::Base.should_receive(:establish_adapter).with('writer')
|
58
|
+
ActiveRecord::Base.should_receive(:establish_adapter).with('reader').twice
|
59
|
+
|
60
|
+
ActiveRecord::Base.seamless_database_pool_connection(options).should == pool_connection
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should raise an error if the adapter would be recursive" do
|
64
|
+
lambda{ActiveRecord::Base.seamless_database_pool_connection('seamless_database_pool').should_raise(ActiveRecord::AdapterNotFound)}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "SeamlessDatabasePoolAdapter" do
|
69
|
+
|
70
|
+
before(:each) do
|
71
|
+
@master_connection = SeamlessDatabasePool::MockMasterConnection.new("master")
|
72
|
+
@read_connection_1 = SeamlessDatabasePool::MockConnection.new("read_1")
|
73
|
+
@read_connection_2 = SeamlessDatabasePool::MockConnection.new("read_2")
|
74
|
+
weights = {@master_connection => 1, @read_connection_1 => 1, @read_connection_2 => 2}
|
75
|
+
@pool_connection = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.new(nil, mock(:logger), @master_connection, [@read_connection_1, @read_connection_2], weights)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should initialize the connection pool" do
|
79
|
+
@pool_connection.master_connection.should == @master_connection
|
80
|
+
@pool_connection.read_connections.should == [@read_connection_1, @read_connection_2]
|
81
|
+
@pool_connection.all_connections.should == [@master_connection, @read_connection_1, @read_connection_2]
|
82
|
+
@pool_connection.pool_weight(@master_connection).should == 1
|
83
|
+
@pool_connection.pool_weight(@read_connection_1).should == 1
|
84
|
+
@pool_connection.pool_weight(@read_connection_2).should == 2
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return the current read connection" do
|
88
|
+
SeamlessDatabasePool.should_receive(:read_only_connection).with(@pool_connection).and_return(:current)
|
89
|
+
@pool_connection.current_read_connection.should == :current
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should select a random read connection" do
|
93
|
+
mock_connection = stub(:connection, :active? => true)
|
94
|
+
@pool_connection.should_receive(:available_read_connections).and_return([:fake1, :fake2, mock_connection])
|
95
|
+
@pool_connection.should_receive(:rand).with(3).and_return(2)
|
96
|
+
@pool_connection.random_read_connection.should == mock_connection
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should select the master connection if the read pool is empty" do
|
100
|
+
@pool_connection.should_receive(:available_read_connections).and_return([])
|
101
|
+
@pool_connection.random_read_connection.should == @master_connection
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should use the master connection in a block" do
|
105
|
+
connection = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.new(nil, mock(:logger), @master_connection, [@read_connection_1], {@read_connection_1 => 1})
|
106
|
+
connection.random_read_connection.should == @read_connection_1
|
107
|
+
connection.use_master_connection do
|
108
|
+
connection.random_read_connection.should == @master_connection
|
109
|
+
end
|
110
|
+
connection.random_read_connection.should == @read_connection_1
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should use the master connection inside a transaction" do
|
114
|
+
connection = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.new(nil, mock(:logger), @master_connection, [@read_connection_1], {@read_connection_1 => 1})
|
115
|
+
@master_connection.should_receive(:transaction).with(true).and_yield
|
116
|
+
@master_connection.should_receive(:select_one).with('Transaction SQL')
|
117
|
+
@read_connection_1.should_receive(:select_one).with('SQL 1')
|
118
|
+
@read_connection_1.should_receive(:select_one).with('SQL 2')
|
119
|
+
|
120
|
+
SeamlessDatabasePool.use_persistent_read_connection do
|
121
|
+
connection.select_one('SQL 1')
|
122
|
+
connection.transaction do
|
123
|
+
connection.select_one('Transaction SQL')
|
124
|
+
end
|
125
|
+
connection.select_one('SQL 2')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Read methods
|
130
|
+
|
131
|
+
it "should proxy select methods to a read connection" do
|
132
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
133
|
+
@read_connection_1.should_receive(:select_one).with('SQL').and_return(:retval)
|
134
|
+
@pool_connection.select_one('SQL').should == :retval
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should proxy select methods to a read connection" do
|
138
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
139
|
+
@read_connection_1.should_receive(:select_all).with('SQL').and_return(:retval)
|
140
|
+
@pool_connection.select_all('SQL').should == :retval
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should proxy select methods to a read connection" do
|
144
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
145
|
+
@read_connection_1.should_receive(:select_value).with('SQL').and_return(:retval)
|
146
|
+
@pool_connection.select_value('SQL').should == :retval
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should proxy select methods to a read connection" do
|
150
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
151
|
+
@read_connection_1.should_receive(:select_values).with('SQL').and_return(:retval)
|
152
|
+
@pool_connection.select_values('SQL').should == :retval
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should proxy select methods to a read connection" do
|
156
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
157
|
+
@read_connection_1.should_receive(:select_rows).with('SQL').and_return(:retval)
|
158
|
+
@pool_connection.select_rows('SQL').should == :retval
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should proxy select methods to a read connection" do
|
162
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
163
|
+
@read_connection_1.should_receive(:select).with('SQL', 'name').and_return(:retval)
|
164
|
+
@pool_connection.send(:select, 'SQL', 'name').should == :retval
|
165
|
+
end
|
166
|
+
|
167
|
+
# Master connection methods
|
168
|
+
|
169
|
+
it "should proxy quote method to the master connection" do
|
170
|
+
@master_connection.should_receive(:insert).with('SQL').and_return(:retval)
|
171
|
+
@pool_connection.insert('SQL').should == :retval
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should proxy quote method to the master connection" do
|
175
|
+
@master_connection.should_receive(:update).with('SQL').and_return(:retval)
|
176
|
+
@pool_connection.update('SQL').should == :retval
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should proxy quote method to the master connection" do
|
180
|
+
@master_connection.should_receive(:execute).with('SQL').and_return(:retval)
|
181
|
+
@pool_connection.execute('SQL').should == :retval
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should proxy quote method to the master connection" do
|
185
|
+
@master_connection.should_receive(:columns).with(:table).and_return(:retval)
|
186
|
+
@pool_connection.columns(:table).should == :retval
|
187
|
+
end
|
188
|
+
|
189
|
+
# Fork to all connection methods
|
190
|
+
|
191
|
+
it "should fork active? to all connections and return true if all are up" do
|
192
|
+
@master_connection.should_receive(:active?).and_return(true)
|
193
|
+
@read_connection_1.should_receive(:active?).and_return(true)
|
194
|
+
@read_connection_2.should_receive(:active?).and_return(true)
|
195
|
+
@pool_connection.active?.should == true
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should fork active? to all connections and return false if one is down" do
|
199
|
+
@master_connection.should_receive(:active?).and_return(true)
|
200
|
+
@read_connection_1.should_receive(:active?).and_return(true)
|
201
|
+
@read_connection_2.should_receive(:active?).and_return(false)
|
202
|
+
@pool_connection.active?.should == false
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should fork verify! to all connections" do
|
206
|
+
@master_connection.should_receive(:verify!).with(5)
|
207
|
+
@read_connection_1.should_receive(:verify!).with(5)
|
208
|
+
@read_connection_2.should_receive(:verify!).with(5)
|
209
|
+
@pool_connection.verify!(5)
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should fork disconnect! to all connections" do
|
213
|
+
@master_connection.should_receive(:disconnect!)
|
214
|
+
@read_connection_1.should_receive(:disconnect!)
|
215
|
+
@read_connection_2.should_receive(:disconnect!)
|
216
|
+
@pool_connection.disconnect!
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should fork reconnect! to all connections" do
|
220
|
+
@master_connection.should_receive(:reconnect!)
|
221
|
+
@read_connection_1.should_receive(:reconnect!)
|
222
|
+
@read_connection_2.should_receive(:reconnect!)
|
223
|
+
@pool_connection.reconnect!
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should timeout reconnect! calls to dead servers" do
|
227
|
+
@read_connection_1.connect_timeout = 0.1
|
228
|
+
lambda{@pool_connection.reconnect!}.should raise_error("reconnect timed out")
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should fork reset_runtime to all connections" do
|
232
|
+
@master_connection.should_receive(:reset_runtime).and_return(1)
|
233
|
+
@read_connection_1.should_receive(:reset_runtime).and_return(2)
|
234
|
+
@read_connection_2.should_receive(:reset_runtime).and_return(3)
|
235
|
+
@pool_connection.reset_runtime.should == 6
|
236
|
+
end
|
237
|
+
|
238
|
+
# Reconnection logic
|
239
|
+
|
240
|
+
it "should proxy requests to a connection" do
|
241
|
+
args = [:arg1, :arg2]
|
242
|
+
block = Proc.new{}
|
243
|
+
@master_connection.should_receive(:select_value).with(*args, &block)
|
244
|
+
@master_connection.should_not_receive(:active?)
|
245
|
+
@master_connection.should_not_receive(:reconnect!)
|
246
|
+
@pool_connection.send(:proxy_connection_method, @master_connection, :select_value, :master, *args, &block)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should try to reconnect dead connections" do
|
250
|
+
args = [:arg1, :arg2]
|
251
|
+
block = Proc.new{}
|
252
|
+
@master_connection.should_receive(:select_value).with(*args, &block).and_raise("SQL ERROR")
|
253
|
+
@master_connection.should_receive(:active?).and_return(false)
|
254
|
+
@master_connection.should_receive(:reconnect!)
|
255
|
+
lambda{@pool_connection.send(:proxy_connection_method, @master_connection, :select_value, :master, *args, &block)}.should raise_error(ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter::DatabaseConnectionError)
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should not try to reconnect live connections" do
|
259
|
+
args = [:arg1, :arg2]
|
260
|
+
block = Proc.new{}
|
261
|
+
@master_connection.should_receive(:select_value).with(*args, &block).and_raise("SQL ERROR")
|
262
|
+
@master_connection.should_receive(:active?).and_return(true)
|
263
|
+
@master_connection.should_not_receive(:reconnect!)
|
264
|
+
lambda{@pool_connection.send(:proxy_connection_method, @master_connection, :select_value, :master, *args, &block)}.should raise_error("SQL ERROR")
|
265
|
+
end
|
266
|
+
|
267
|
+
it "should not try to reconnect a connection during a retry" do
|
268
|
+
args = [:arg1, :arg2]
|
269
|
+
block = Proc.new{}
|
270
|
+
@master_connection.should_receive(:select_value).with(*args, &block).and_raise("SQL ERROR")
|
271
|
+
@master_connection.should_not_receive(:active?)
|
272
|
+
@master_connection.should_not_receive(:reconnect!)
|
273
|
+
lambda{@pool_connection.send(:proxy_connection_method, @master_connection, :select_value, :retry, *args, &block)}.should raise_error("SQL ERROR")
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should try to execute a read statement again after a connection error" do
|
277
|
+
connection_error = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter::DatabaseConnectionError.new
|
278
|
+
connection_error.wrapped_exception = StandardError.new("Error")
|
279
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
280
|
+
@pool_connection.should_receive(:proxy_connection_method).with(@read_connection_1, :select_value, :read, 'SQL').and_raise(connection_error)
|
281
|
+
@read_connection_1.should_receive(:active?).and_return(true)
|
282
|
+
@pool_connection.should_not_receive(:suppress_read_connection)
|
283
|
+
SeamlessDatabasePool.should_not_receive(:set_persistent_read_connection)
|
284
|
+
@pool_connection.should_receive(:proxy_connection_method).with(@read_connection_1, :select_value, :retry, 'SQL').and_return(:results)
|
285
|
+
@pool_connection.select_value('SQL').should == :results
|
286
|
+
end
|
287
|
+
|
288
|
+
it "should not try to execute a read statement again after a connection error if the master connection must be used" do
|
289
|
+
connection_error = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter::DatabaseConnectionError.new
|
290
|
+
connection_error.wrapped_exception = StandardError.new("Error")
|
291
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
292
|
+
@pool_connection.should_receive(:proxy_connection_method).with(@read_connection_1, :select_value, :read, 'SQL').and_raise(connection_error)
|
293
|
+
@pool_connection.use_master_connection do
|
294
|
+
lambda{@pool_connection.select_value('SQL')}.should raise_error("Error")
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
it "should not try to execute a read statement again after a connection error if there is a block" do
|
299
|
+
connection_error = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter::DatabaseConnectionError.new
|
300
|
+
connection_error.wrapped_exception = StandardError.new("Error")
|
301
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
302
|
+
@pool_connection.should_receive(:proxy_connection_method).with(@read_connection_1, :select_value, :read, 'SQL').and_raise(connection_error)
|
303
|
+
lambda{@pool_connection.select_value('SQL'){}}.should raise_error("Error")
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should not try to execute a read statement again after a non-connection error" do
|
307
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1)
|
308
|
+
@pool_connection.should_receive(:proxy_connection_method).with(@read_connection_1, :select_value, :read, 'SQL').and_raise("SQL Error")
|
309
|
+
lambda{@pool_connection.select_value('SQL')}.should raise_error("SQL Error")
|
310
|
+
end
|
311
|
+
|
312
|
+
it "should use a different connection on a retry if the original connection could not be reconnected" do
|
313
|
+
connection_error = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter::DatabaseConnectionError.new
|
314
|
+
connection_error.wrapped_exception = StandardError.new("Error")
|
315
|
+
@pool_connection.should_receive(:current_read_connection).and_return(@read_connection_1, @read_connection_2)
|
316
|
+
@pool_connection.should_receive(:proxy_connection_method).with(@read_connection_1, :select_value, :read, 'SQL').and_raise(connection_error)
|
317
|
+
@read_connection_1.should_receive(:active?).and_return(false)
|
318
|
+
@pool_connection.should_receive(:suppress_read_connection).with(@read_connection_1, 30)
|
319
|
+
SeamlessDatabasePool.should_receive(:set_persistent_read_connection).with(@pool_connection, @read_connection_2)
|
320
|
+
@pool_connection.should_receive(:proxy_connection_method).with(@read_connection_2, :select_value, :retry, 'SQL').and_return(:results)
|
321
|
+
@pool_connection.select_value('SQL').should == :results
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should keep track of read connections that can't be reconnected for a set period" do
|
325
|
+
@pool_connection.available_read_connections.should include(@read_connection_1)
|
326
|
+
@pool_connection.suppress_read_connection(@read_connection_1, 30)
|
327
|
+
@pool_connection.available_read_connections.should_not include(@read_connection_1)
|
328
|
+
end
|
329
|
+
|
330
|
+
it "should return dead connections to the pool after the timeout has expired" do
|
331
|
+
@pool_connection.available_read_connections.should include(@read_connection_1)
|
332
|
+
@pool_connection.suppress_read_connection(@read_connection_1, 0.2)
|
333
|
+
@pool_connection.available_read_connections.should_not include(@read_connection_1)
|
334
|
+
sleep(0.3)
|
335
|
+
@pool_connection.available_read_connections.should include(@read_connection_1)
|
336
|
+
end
|
337
|
+
|
338
|
+
it "should not return a connection to the pool until it can be reconnected" do
|
339
|
+
@pool_connection.available_read_connections.should include(@read_connection_1)
|
340
|
+
@pool_connection.suppress_read_connection(@read_connection_1, 0.2)
|
341
|
+
@pool_connection.available_read_connections.should_not include(@read_connection_1)
|
342
|
+
sleep(0.3)
|
343
|
+
@read_connection_1.should_receive(:reconnect!)
|
344
|
+
@read_connection_1.should_receive(:active?).and_return(false)
|
345
|
+
@pool_connection.available_read_connections.should_not include(@read_connection_1)
|
346
|
+
end
|
347
|
+
|
348
|
+
it "should try all connections again if none of them can be reconnected" do
|
349
|
+
stack = @pool_connection.instance_variable_get(:@available_read_connections)
|
350
|
+
|
351
|
+
available = @pool_connection.available_read_connections
|
352
|
+
available.should include(@read_connection_1)
|
353
|
+
available.should include(@read_connection_2)
|
354
|
+
available.should include(@master_connection)
|
355
|
+
stack.size.should == 1
|
356
|
+
|
357
|
+
@pool_connection.suppress_read_connection(@read_connection_1, 30)
|
358
|
+
available = @pool_connection.available_read_connections
|
359
|
+
available.should_not include(@read_connection_1)
|
360
|
+
available.should include(@read_connection_2)
|
361
|
+
available.should include(@master_connection)
|
362
|
+
stack.size.should == 2
|
363
|
+
|
364
|
+
@pool_connection.suppress_read_connection(@master_connection, 30)
|
365
|
+
available = @pool_connection.available_read_connections
|
366
|
+
available.should_not include(@read_connection_1)
|
367
|
+
available.should include(@read_connection_2)
|
368
|
+
available.should_not include(@master_connection)
|
369
|
+
stack.size.should == 3
|
370
|
+
|
371
|
+
@pool_connection.suppress_read_connection(@read_connection_2, 30)
|
372
|
+
available = @pool_connection.available_read_connections
|
373
|
+
available.should include(@read_connection_1)
|
374
|
+
available.should include(@read_connection_2)
|
375
|
+
available.should include(@master_connection)
|
376
|
+
stack.size.should == 1
|
377
|
+
end
|
378
|
+
|
379
|
+
it "should not try to suppress a read connection that wasn't available in the read pool" do
|
380
|
+
stack = @pool_connection.instance_variable_get(:@available_read_connections)
|
381
|
+
stack.size.should == 1
|
382
|
+
@pool_connection.suppress_read_connection(@read_connection_1, 30)
|
383
|
+
stack.size.should == 2
|
384
|
+
@pool_connection.suppress_read_connection(@read_connection_1, 30)
|
385
|
+
stack.size.should == 2
|
386
|
+
end
|
387
|
+
|
388
|
+
end
|
389
|
+
|
390
|
+
describe "Reload extensions" do
|
391
|
+
before(:all) do
|
392
|
+
ActiveRecord::Migration.suppress_messages do
|
393
|
+
class SeamlessDatabasePoolTestThing < ActiveRecord::Base
|
394
|
+
ActiveRecord::Migration.create_table(:seamless_database_pool_test_things) do |t|
|
395
|
+
t.column :name, :string
|
396
|
+
t.column :seamless_database_pool_test_model_id, :integer
|
397
|
+
end unless table_exists?
|
398
|
+
belongs_to :seamless_database_pool_test_model
|
399
|
+
end
|
400
|
+
|
401
|
+
class SeamlessDatabasePoolTestModel < ActiveRecord::Base
|
402
|
+
ActiveRecord::Migration.create_table(:seamless_database_pool_test_models) do |t|
|
403
|
+
t.column :name, :string
|
404
|
+
end unless table_exists?
|
405
|
+
has_many :seamless_database_pool_test_things
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
@record = SeamlessDatabasePoolTestModel.create(:name => 'test')
|
410
|
+
@thing = SeamlessDatabasePoolTestThing.create(:name => 'thing', :seamless_database_pool_test_model_id => @record.id)
|
411
|
+
end
|
412
|
+
|
413
|
+
after(:all) do
|
414
|
+
ActiveRecord::Migration.suppress_messages do
|
415
|
+
ActiveRecord::Migration.drop_table(:seamless_database_pool_test_models) if SeamlessDatabasePoolTestModel.table_exists?
|
416
|
+
ActiveRecord::Migration.drop_table(:seamless_database_pool_test_things) if SeamlessDatabasePoolTestThing.table_exists?
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
it "should force the master connection on reload" do
|
421
|
+
SeamlessDatabasePool.should_receive(:use_master_connection).and_yield
|
422
|
+
@record.should_receive(:reload_without_seamless_database_pool).with(:options)
|
423
|
+
@record.reload(:options)
|
424
|
+
end
|
425
|
+
|
426
|
+
it "should force the master connection on reload of an association" do
|
427
|
+
association = @record.seamless_database_pool_test_things
|
428
|
+
SeamlessDatabasePool.should_receive(:use_master_connection).and_yield
|
429
|
+
association.should_receive(:reload_without_seamless_database_pool)
|
430
|
+
@record.seamless_database_pool_test_things(true)
|
431
|
+
end
|
432
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe "SeamlessDatabasePool" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
SeamlessDatabasePool.clear_read_only_connection
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:each) do
|
10
|
+
SeamlessDatabasePool.clear_read_only_connection
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should use the master connection by default" do
|
14
|
+
connection = stub(:connection, :master_connection => :master_db_connection)
|
15
|
+
connection.stub!(:using_master_connection?).and_return(false)
|
16
|
+
SeamlessDatabasePool.read_only_connection_type.should == :master
|
17
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be able to set using persistent read connections" do
|
21
|
+
connection = mock(:connection)
|
22
|
+
connection.should_receive(:random_read_connection).once.and_return(:read_db_connection)
|
23
|
+
connection.stub!(:using_master_connection?).and_return(false)
|
24
|
+
SeamlessDatabasePool.use_persistent_read_connection
|
25
|
+
SeamlessDatabasePool.read_only_connection_type.should == :persistent
|
26
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
27
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should be able to set using random read connections" do
|
31
|
+
connection = mock(:connection)
|
32
|
+
connection.should_receive(:random_read_connection).and_return(:read_db_connection_1, :read_db_connection_2)
|
33
|
+
connection.stub!(:using_master_connection?).and_return(false)
|
34
|
+
SeamlessDatabasePool.use_random_read_connection
|
35
|
+
SeamlessDatabasePool.read_only_connection_type.should == :random
|
36
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection_1
|
37
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection_2
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should use the master connection if the connection is forcing it" do
|
41
|
+
connection = stub(:connection, :master_connection => :master_db_connection)
|
42
|
+
connection.should_receive(:using_master_connection?).and_return(true)
|
43
|
+
SeamlessDatabasePool.use_persistent_read_connection
|
44
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should be able to set using the master connection" do
|
48
|
+
connection = stub(:connection, :master_connection => :master_db_connection)
|
49
|
+
connection.stub!(:using_master_connection?).and_return(false)
|
50
|
+
SeamlessDatabasePool.use_master_connection
|
51
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be able to use persistent read connections within a block" do
|
55
|
+
connection = stub(:connection, :master_connection => :master_db_connection)
|
56
|
+
connection.should_receive(:random_read_connection).once.and_return(:read_db_connection)
|
57
|
+
connection.stub!(:using_master_connection?).and_return(false)
|
58
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
59
|
+
SeamlessDatabasePool.use_persistent_read_connection do
|
60
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
61
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
62
|
+
:test_val
|
63
|
+
end.should == :test_val
|
64
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should be able to use random read connections within a block" do
|
68
|
+
connection = stub(:connection, :master_connection => :master_db_connection)
|
69
|
+
connection.should_receive(:random_read_connection).and_return(:read_db_connection_1, :read_db_connection_2)
|
70
|
+
connection.stub!(:using_master_connection?).and_return(false)
|
71
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
72
|
+
SeamlessDatabasePool.use_random_read_connection do
|
73
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection_1
|
74
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection_2
|
75
|
+
:test_val
|
76
|
+
end.should == :test_val
|
77
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should be able to use the master connection within a block" do
|
81
|
+
connection = stub(:connection, :master_connection => :master_db_connection)
|
82
|
+
connection.should_receive(:random_read_connection).once.and_return(:read_db_connection)
|
83
|
+
connection.stub!(:using_master_connection?).and_return(false)
|
84
|
+
SeamlessDatabasePool.use_persistent_read_connection
|
85
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
86
|
+
SeamlessDatabasePool.use_master_connection do
|
87
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
88
|
+
:test_val
|
89
|
+
end.should == :test_val
|
90
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
91
|
+
SeamlessDatabasePool.clear_read_only_connection
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should be able to use connection blocks within connection blocks" do
|
95
|
+
connection = stub(:connection, :master_connection => :master_db_connection)
|
96
|
+
connection.should_receive(:random_read_connection).any_number_of_times.and_return(:read_db_connection)
|
97
|
+
connection.stub!(:using_master_connection?).and_return(false)
|
98
|
+
SeamlessDatabasePool.use_persistent_read_connection do
|
99
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
100
|
+
SeamlessDatabasePool.use_master_connection do
|
101
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
102
|
+
SeamlessDatabasePool.use_random_read_connection do
|
103
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
104
|
+
end
|
105
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :master_db_connection
|
106
|
+
end
|
107
|
+
end
|
108
|
+
SeamlessDatabasePool.clear_read_only_connection
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should be able to change the persistent connection" do
|
112
|
+
connection = mock(:connection)
|
113
|
+
connection.stub!(:random_read_connection).and_return(:read_db_connection)
|
114
|
+
connection.stub!(:using_master_connection?).and_return(false)
|
115
|
+
|
116
|
+
SeamlessDatabasePool.use_persistent_read_connection
|
117
|
+
SeamlessDatabasePool.read_only_connection_type.should == :persistent
|
118
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
119
|
+
SeamlessDatabasePool.set_persistent_read_connection(connection, :another_db_connection)
|
120
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :another_db_connection
|
121
|
+
|
122
|
+
SeamlessDatabasePool.use_random_read_connection
|
123
|
+
SeamlessDatabasePool.read_only_connection_type.should == :random
|
124
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
125
|
+
SeamlessDatabasePool.set_persistent_read_connection(connection, :another_db_connection)
|
126
|
+
SeamlessDatabasePool.read_only_connection(connection).should == :read_db_connection
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should be able to specify a default read connection type instead of :master" do
|
130
|
+
SeamlessDatabasePool.read_only_connection_type.should == :master
|
131
|
+
SeamlessDatabasePool.read_only_connection_type(nil).should == nil
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|