master_slave_adapter 1.0.0.beta1 → 1.0.0.beta2
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/.gitignore +2 -1
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +4 -1
- data/LICENSE +2 -0
- data/Rakefile +51 -2
- data/Readme.md +55 -37
- data/lib/active_record/connection_adapters/master_slave_adapter/clock.rb +0 -1
- data/lib/active_record/connection_adapters/master_slave_adapter/shared_mysql_adapter_behavior.rb +43 -0
- data/lib/active_record/connection_adapters/master_slave_adapter/version.rb +1 -1
- data/lib/active_record/connection_adapters/master_slave_adapter.rb +347 -354
- data/lib/active_record/connection_adapters/mysql2_master_slave_adapter.rb +48 -0
- data/lib/active_record/connection_adapters/mysql_master_slave_adapter.rb +22 -41
- data/master_slave_adapter.gemspec +3 -3
- data/spec/all.sh +15 -0
- data/spec/gemfiles/activerecord2.3 +5 -0
- data/spec/gemfiles/activerecord3.0 +5 -0
- data/spec/gemfiles/activerecord3.2 +6 -0
- data/spec/integration/helpers/mysql_helper.rb +174 -0
- data/spec/integration/helpers/shared_mysql_examples.rb +212 -0
- data/spec/integration/mysql2_master_slave_adapter_spec.rb +11 -0
- data/spec/integration/mysql_master_slave_adapter_spec.rb +11 -0
- data/spec/master_slave_adapter_spec.rb +75 -21
- data/spec/mysql2_master_slave_adapter_spec.rb +372 -0
- data/spec/mysql_master_slave_adapter_spec.rb +116 -72
- metadata +40 -15
- data/Gemfile +0 -3
- data/TODO.txt +0 -18
@@ -1,11 +1,9 @@
|
|
1
1
|
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
|
3
3
|
require 'rspec'
|
4
|
+
require 'logger'
|
4
5
|
require 'active_record/connection_adapters/master_slave_adapter'
|
5
6
|
|
6
|
-
ActiveRecord::Base.logger =
|
7
|
-
Logger.new($stdout).tap { |l| l.level = Logger::DEBUG }
|
8
|
-
|
9
7
|
module ActiveRecord
|
10
8
|
class Base
|
11
9
|
cattr_accessor :master_mock, :slave_mock
|
@@ -14,12 +12,14 @@ module ActiveRecord
|
|
14
12
|
end
|
15
13
|
|
16
14
|
def self.test_master_slave_connection(config)
|
17
|
-
TestMasterSlaveAdapter.new(config, logger)
|
15
|
+
ConnectionAdapters::TestMasterSlaveAdapter.new(config, logger)
|
18
16
|
end
|
19
17
|
end
|
20
18
|
|
21
19
|
module ConnectionAdapters
|
22
|
-
class TestMasterSlaveAdapter <
|
20
|
+
class TestMasterSlaveAdapter < AbstractAdapter
|
21
|
+
include MasterSlaveAdapter
|
22
|
+
|
23
23
|
def master_clock
|
24
24
|
end
|
25
25
|
|
@@ -77,7 +77,7 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
|
|
77
77
|
end
|
78
78
|
|
79
79
|
SchemaStatements = ActiveRecord::ConnectionAdapters::SchemaStatements.public_instance_methods.map(&:to_sym)
|
80
|
-
SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ]
|
80
|
+
SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ] unless defined?(SelectMethods)
|
81
81
|
|
82
82
|
before do
|
83
83
|
ActiveRecord::Base.establish_connection(database_setup)
|
@@ -114,19 +114,31 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
117
|
+
context "given there are open transactions" do
|
118
|
+
it "should send the method '#{method}' to the master connection" do
|
119
|
+
master_connection.stub!( :open_transactions ).and_return( 1 )
|
120
|
+
master_connection.should_receive( method ).with('testing').and_return( true )
|
121
|
+
|
121
122
|
adapter_connection.send( method, 'testing' )
|
122
123
|
end
|
123
|
-
end
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
125
|
+
it "should send the method '#{method}' to the master connection, even in with_slave" do
|
126
|
+
master_connection.stub!( :open_transactions ).and_return( 1 )
|
127
|
+
master_connection.should_receive( method ).with('testing').and_return( true )
|
128
|
+
|
129
|
+
ActiveRecord::Base.with_slave do
|
130
|
+
adapter_connection.send( method, 'testing' )
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it "raises MasterUnavailable if master is not available" do
|
135
|
+
master_connection.stub(:open_transactions).and_return(1)
|
136
|
+
master_connection.stub(:connection_error?).and_return(true)
|
137
|
+
master_connection.should_receive(method).with('testing').and_raise(ActiveRecord::StatementInvalid)
|
138
|
+
|
139
|
+
expect do
|
140
|
+
adapter_connection.send(method, 'testing')
|
141
|
+
end.to raise_error(ActiveRecord::MasterUnavailable)
|
130
142
|
end
|
131
143
|
end
|
132
144
|
end # /SelectMethods.each
|
@@ -136,12 +148,14 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
|
|
136
148
|
master_connection.should_receive( method ).and_return( true )
|
137
149
|
adapter_connection.send( method )
|
138
150
|
end
|
139
|
-
end
|
140
151
|
|
141
|
-
|
142
|
-
|
143
|
-
master_connection.should_receive(
|
144
|
-
|
152
|
+
it "should raise MasterSlaveAdapter if master is not available" do
|
153
|
+
master_connection.stub(:connection_error?).and_return(true)
|
154
|
+
master_connection.should_receive(method).and_raise(ActiveRecord::StatementInvalid)
|
155
|
+
|
156
|
+
expect do
|
157
|
+
adapter_connection.send(method)
|
158
|
+
end.to raise_error(ActiveRecord::MasterUnavailable)
|
145
159
|
end
|
146
160
|
end
|
147
161
|
|
@@ -315,4 +329,44 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
|
|
315
329
|
end
|
316
330
|
end
|
317
331
|
end
|
332
|
+
|
333
|
+
describe "connection stack" do
|
334
|
+
it "should start with the slave connection on top" do
|
335
|
+
adapter_connection.current_connection.should == slave_connection
|
336
|
+
end
|
337
|
+
|
338
|
+
it "should keep the current connection on top" do
|
339
|
+
ActiveRecord::Base.with_master do
|
340
|
+
adapter_connection.current_connection.should == master_connection
|
341
|
+
ActiveRecord::Base.with_slave do
|
342
|
+
adapter_connection.current_connection.should == slave_connection
|
343
|
+
ActiveRecord::Base.with_master do
|
344
|
+
adapter_connection.current_connection.should == master_connection
|
345
|
+
end
|
346
|
+
adapter_connection.current_connection.should == slave_connection
|
347
|
+
end
|
348
|
+
adapter_connection.current_connection.should == master_connection
|
349
|
+
end
|
350
|
+
adapter_connection.current_connection.should == slave_connection
|
351
|
+
end
|
352
|
+
|
353
|
+
it "should continue to use master connection after a write" do
|
354
|
+
master_connection.should_receive(:execute).with("INSERT 42")
|
355
|
+
|
356
|
+
ActiveRecord::Base.with_slave do
|
357
|
+
adapter_connection.current_connection.should == slave_connection
|
358
|
+
ActiveRecord::Base.with_master do
|
359
|
+
adapter_connection.current_connection.should == master_connection
|
360
|
+
ActiveRecord::Base.with_slave do
|
361
|
+
adapter_connection.current_connection.should == slave_connection
|
362
|
+
adapter_connection.execute("INSERT 42")
|
363
|
+
adapter_connection.current_connection.should == master_connection
|
364
|
+
end
|
365
|
+
adapter_connection.current_connection.should == master_connection
|
366
|
+
end
|
367
|
+
adapter_connection.current_connection.should == master_connection
|
368
|
+
end
|
369
|
+
adapter_connection.current_connection.should == master_connection
|
370
|
+
end
|
371
|
+
end
|
318
372
|
end
|
@@ -0,0 +1,372 @@
|
|
1
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'logger'
|
5
|
+
require 'active_record/connection_adapters/mysql2_master_slave_adapter'
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
class Base
|
9
|
+
cattr_accessor :master_mock, :slave_mock
|
10
|
+
def self.mysql2_connection(config)
|
11
|
+
config[:database] == 'slave' ? slave_mock : master_mock
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ActiveRecord::ConnectionAdapters::Mysql2MasterSlaveAdapter do
|
17
|
+
let(:database_setup) do
|
18
|
+
{
|
19
|
+
:adapter => 'master_slave',
|
20
|
+
:username => 'root',
|
21
|
+
:database => 'slave',
|
22
|
+
:connection_adapter => 'mysql2',
|
23
|
+
:master => { :username => 'root', :database => 'master' },
|
24
|
+
:slaves => [{ :database => 'slave' }],
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:mocked_methods) do
|
29
|
+
{
|
30
|
+
:reconnect! => true,
|
31
|
+
:disconnect! => true,
|
32
|
+
:active? => true,
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
let!(:master_connection) do
|
37
|
+
mock(
|
38
|
+
'master connection',
|
39
|
+
mocked_methods.merge(:open_transactions => 0)
|
40
|
+
).tap do |conn|
|
41
|
+
conn.stub!(:uncached).and_yield
|
42
|
+
ActiveRecord::Base.master_mock = conn
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
let!(:slave_connection) do
|
47
|
+
mock('slave connection', mocked_methods).tap do |conn|
|
48
|
+
conn.stub!(:uncached).and_yield
|
49
|
+
ActiveRecord::Base.slave_mock = conn
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def adapter_connection
|
54
|
+
ActiveRecord::Base.connection
|
55
|
+
end
|
56
|
+
|
57
|
+
SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ] unless defined?(SelectMethods)
|
58
|
+
Clock = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock unless defined?(Clock)
|
59
|
+
|
60
|
+
before do
|
61
|
+
ActiveRecord::Base.establish_connection(database_setup)
|
62
|
+
end
|
63
|
+
|
64
|
+
after do
|
65
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'consistency' do
|
69
|
+
def zero
|
70
|
+
Clock.zero
|
71
|
+
end
|
72
|
+
|
73
|
+
def master_position(pos)
|
74
|
+
Clock.new('', pos)
|
75
|
+
end
|
76
|
+
|
77
|
+
def select_method
|
78
|
+
:select_one
|
79
|
+
end
|
80
|
+
|
81
|
+
def should_report_clock(pos, connection, log_file, log_pos, sql)
|
82
|
+
pos = Array(pos)
|
83
|
+
values = pos.map { |p| { log_file => '', log_pos => p } }
|
84
|
+
|
85
|
+
connection.
|
86
|
+
should_receive(select_method).exactly(pos.length).times.
|
87
|
+
with(sql).
|
88
|
+
and_return(*values)
|
89
|
+
end
|
90
|
+
|
91
|
+
def slave_should_report_clock(pos)
|
92
|
+
should_report_clock(pos, slave_connection, 'Relay_Master_Log_File', 'Exec_Master_Log_Pos', 'SHOW SLAVE STATUS')
|
93
|
+
end
|
94
|
+
|
95
|
+
def master_should_report_clock(pos)
|
96
|
+
should_report_clock(pos, master_connection, 'File', 'Position', 'SHOW MASTER STATUS')
|
97
|
+
end
|
98
|
+
|
99
|
+
SelectMethods.each do |method|
|
100
|
+
it "should send the method '#{method}' to the slave if nil is given" do
|
101
|
+
slave_should_report_clock(0)
|
102
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
103
|
+
new_clock = ActiveRecord::Base.with_consistency(nil) do
|
104
|
+
adapter_connection.send(method, 'testing')
|
105
|
+
end
|
106
|
+
new_clock.should be_a(Clock)
|
107
|
+
new_clock.should equal(zero)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should send the method '#{method}' to the slave if clock.zero is given" do
|
111
|
+
slave_should_report_clock(0)
|
112
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
113
|
+
old_clock = zero
|
114
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
115
|
+
adapter_connection.send(method, 'testing')
|
116
|
+
end
|
117
|
+
new_clock.should be_a(Clock)
|
118
|
+
new_clock.should equal(old_clock)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should send the method '#{method}' to the master if slave hasn't cought up to required clock yet" do
|
122
|
+
slave_should_report_clock(0)
|
123
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
124
|
+
old_clock = master_position(1)
|
125
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
126
|
+
adapter_connection.send(method, 'testing' )
|
127
|
+
end
|
128
|
+
new_clock.should be_a(Clock)
|
129
|
+
new_clock.should equal(old_clock)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should send the method '#{method}' to the master connection if there are open transactions" do
|
133
|
+
master_connection.stub!(:open_transactions).and_return(1)
|
134
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
135
|
+
old_clock = zero
|
136
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
137
|
+
adapter_connection.send(method, 'testing')
|
138
|
+
end
|
139
|
+
new_clock.should be_a(Clock)
|
140
|
+
new_clock.should equal(zero)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should send the method '#{method}' to the master after a write operation" do
|
144
|
+
slave_should_report_clock(0)
|
145
|
+
master_should_report_clock(2)
|
146
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
147
|
+
master_connection.should_receive(:update).with('testing').and_return(true)
|
148
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
149
|
+
old_clock = zero
|
150
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
151
|
+
adapter_connection.send(method, 'testing') # slave
|
152
|
+
adapter_connection.send(:update, 'testing') # master
|
153
|
+
adapter_connection.send(method, 'testing') # master
|
154
|
+
end
|
155
|
+
new_clock.should be_a(Clock)
|
156
|
+
new_clock.should > old_clock
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should update the clock after a transaction" do
|
161
|
+
slave_should_report_clock(0)
|
162
|
+
master_should_report_clock([0, 1, 1])
|
163
|
+
|
164
|
+
slave_connection.
|
165
|
+
should_receive(:select_all).exactly(1).times.with('testing').
|
166
|
+
and_return(true)
|
167
|
+
|
168
|
+
master_connection.
|
169
|
+
should_receive(:update).exactly(3).times.with('testing').
|
170
|
+
and_return(true)
|
171
|
+
master_connection.
|
172
|
+
should_receive(:select_all).exactly(5).times.with('testing').
|
173
|
+
and_return(true)
|
174
|
+
%w(begin_db_transaction
|
175
|
+
commit_db_transaction
|
176
|
+
increment_open_transactions
|
177
|
+
decrement_open_transactions
|
178
|
+
outside_transaction?).each do |txstmt|
|
179
|
+
master_connection.should_receive(txstmt).exactly(1).times
|
180
|
+
end
|
181
|
+
|
182
|
+
master_connection.
|
183
|
+
should_receive('open_transactions').exactly(13).times.
|
184
|
+
and_return(
|
185
|
+
# adapter: with_consistency, select_all, update, select_all
|
186
|
+
0, 0, 0, 0,
|
187
|
+
# connection: transaction
|
188
|
+
0,
|
189
|
+
# adapter: select_all, update, select_all, commit_db_transaction
|
190
|
+
1, 1, 1, 0,
|
191
|
+
# connection: transaction (ensure)
|
192
|
+
0,
|
193
|
+
# adapter: select_all, update, select_all
|
194
|
+
0, 0, 0
|
195
|
+
)
|
196
|
+
|
197
|
+
old_clock = zero
|
198
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
199
|
+
adapter_connection.send(:select_all, 'testing') # slave s=0 m=0
|
200
|
+
adapter_connection.send(:update, 'testing') # master s=0 m=1
|
201
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=1
|
202
|
+
|
203
|
+
ActiveRecord::Base.transaction do
|
204
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=1
|
205
|
+
adapter_connection.send(:update, 'testing') # master s=0 m=1
|
206
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=1
|
207
|
+
end
|
208
|
+
|
209
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=2
|
210
|
+
adapter_connection.send(:update, 'testing') # master s=0 m=3
|
211
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=3
|
212
|
+
end
|
213
|
+
|
214
|
+
new_clock.should > old_clock
|
215
|
+
end
|
216
|
+
|
217
|
+
context "with nested with_consistency" do
|
218
|
+
it "should return the same clock if not writing and no lag" do
|
219
|
+
slave_should_report_clock(0)
|
220
|
+
slave_connection.
|
221
|
+
should_receive(:select_one).exactly(3).times.with('testing').
|
222
|
+
and_return(true)
|
223
|
+
|
224
|
+
old_clock = zero
|
225
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
226
|
+
adapter_connection.send(:select_one, 'testing')
|
227
|
+
ActiveRecord::Base.with_consistency(old_clock) do
|
228
|
+
adapter_connection.send(:select_one, 'testing')
|
229
|
+
end
|
230
|
+
adapter_connection.send(:select_one, 'testing')
|
231
|
+
end
|
232
|
+
new_clock.should equal(old_clock)
|
233
|
+
end
|
234
|
+
|
235
|
+
it "requesting a newer clock should return a new clock" do
|
236
|
+
adapter_connection.
|
237
|
+
should_receive('slave_consistent?').exactly(2).times.
|
238
|
+
and_return(true, false)
|
239
|
+
slave_connection.
|
240
|
+
should_receive(:select_all).exactly(2).times.with('testing').
|
241
|
+
and_return(true)
|
242
|
+
master_connection.
|
243
|
+
should_receive(:select_all).exactly(1).times.with('testing').
|
244
|
+
and_return(true)
|
245
|
+
|
246
|
+
start_clock = zero
|
247
|
+
inner_clock = zero
|
248
|
+
outer_clock = ActiveRecord::Base.with_consistency(start_clock) do
|
249
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
250
|
+
inner_clock = ActiveRecord::Base.with_consistency(master_position(1)) do
|
251
|
+
adapter_connection.send(:select_all, 'testing') # master
|
252
|
+
end
|
253
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
254
|
+
end
|
255
|
+
|
256
|
+
start_clock.should equal(outer_clock)
|
257
|
+
inner_clock.should > start_clock
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should do the right thing when nested inside with_master" do
|
262
|
+
slave_should_report_clock(0)
|
263
|
+
slave_connection.should_receive(:select_all).exactly(1).times.with('testing').and_return(true)
|
264
|
+
master_connection.should_receive(:select_all).exactly(2).times.with('testing').and_return(true)
|
265
|
+
ActiveRecord::Base.with_master do
|
266
|
+
adapter_connection.send(:select_all, 'testing') # master
|
267
|
+
ActiveRecord::Base.with_consistency(zero) do
|
268
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
269
|
+
end
|
270
|
+
adapter_connection.send(:select_all, 'testing') # master
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
it "should do the right thing when nested inside with_slave" do
|
275
|
+
slave_should_report_clock(0)
|
276
|
+
slave_connection.should_receive(:select_all).exactly(3).times.with('testing').and_return(true)
|
277
|
+
ActiveRecord::Base.with_slave do
|
278
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
279
|
+
ActiveRecord::Base.with_consistency(zero) do
|
280
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
281
|
+
end
|
282
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
it "should do the right thing when wrapping with_master" do
|
287
|
+
slave_should_report_clock(0)
|
288
|
+
slave_connection.should_receive(:select_all).exactly(2).times.with('testing').and_return(true)
|
289
|
+
master_connection.should_receive(:select_all).exactly(1).times.with('testing').and_return(true)
|
290
|
+
ActiveRecord::Base.with_consistency(zero) do
|
291
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
292
|
+
ActiveRecord::Base.with_master do
|
293
|
+
adapter_connection.send(:select_all, 'testing') # master
|
294
|
+
end
|
295
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
it "should do the right thing when wrapping with_slave" do
|
300
|
+
slave_should_report_clock(0)
|
301
|
+
slave_connection.should_receive(:select_all).exactly(1).times.with('testing').and_return(true)
|
302
|
+
master_connection.should_receive(:select_all).exactly(2).times.with('testing').and_return(true)
|
303
|
+
ActiveRecord::Base.with_consistency(master_position(1)) do
|
304
|
+
adapter_connection.send(:select_all, 'testing') # master
|
305
|
+
ActiveRecord::Base.with_slave do
|
306
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
307
|
+
end
|
308
|
+
adapter_connection.send(:select_all, 'testing') # master
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
it "should accept clock as string" do
|
313
|
+
slave_should_report_clock(0)
|
314
|
+
slave_connection.should_receive(:select_all).with('testing')
|
315
|
+
|
316
|
+
ActiveRecord::Base.with_consistency("@0") do
|
317
|
+
adapter_connection.send(:select_all, 'testing')
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end # /with_consistency
|
321
|
+
|
322
|
+
describe "connection error detection" do
|
323
|
+
{
|
324
|
+
2002 => "query: not connected",
|
325
|
+
2003 => "Can't connect to MySQL server on 'localhost' (3306)",
|
326
|
+
2006 => "MySQL server has gone away",
|
327
|
+
2013 => "Lost connection to MySQL server during query",
|
328
|
+
}.each do |errno, description|
|
329
|
+
it "raises MasterUnavailable for '#{description}' during query execution" do
|
330
|
+
master_connection.stub_chain(:raw_connection, :errno).and_return(errno)
|
331
|
+
master_connection.should_receive(:insert).and_raise(ActiveRecord::StatementInvalid.new("Mysql2::Error: #{description}: INSERT 42"))
|
332
|
+
|
333
|
+
expect do
|
334
|
+
adapter_connection.insert("INSERT 42")
|
335
|
+
end.to raise_error(ActiveRecord::MasterUnavailable)
|
336
|
+
end
|
337
|
+
|
338
|
+
it "doesn't raise anything for '#{description}' during connection" do
|
339
|
+
error = Mysql2::Error.new(description)
|
340
|
+
error.stub(:errno).and_return(errno)
|
341
|
+
ActiveRecord::Base.should_receive(:mysql2_connection).twice do |config|
|
342
|
+
if config[:name] == :master
|
343
|
+
raise error
|
344
|
+
else
|
345
|
+
slave_connection
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
expect do
|
350
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!
|
351
|
+
ActiveRecord::Base.connection
|
352
|
+
end.to_not raise_error
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
it "raises MasterUnavailable for 'closed MySQL connection' during query execution" do
|
357
|
+
master_connection.should_receive(:insert).and_raise(ActiveRecord::StatementInvalid.new("Mysql2::Error: closed MySQL connection: INSERT 42"))
|
358
|
+
|
359
|
+
expect do
|
360
|
+
adapter_connection.insert("INSERT 42")
|
361
|
+
end.to raise_error(ActiveRecord::MasterUnavailable)
|
362
|
+
end
|
363
|
+
|
364
|
+
it "raises StatementInvalid for other errors" do
|
365
|
+
master_connection.should_receive(:insert).and_raise(ActiveRecord::StatementInvalid.new("Mysql2::Error: Query execution was interrupted: INSERT 42"))
|
366
|
+
|
367
|
+
expect do
|
368
|
+
adapter_connection.insert("INSERT 42")
|
369
|
+
end.to raise_error(ActiveRecord::StatementInvalid)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|