master_slave_adapter 1.0.0.beta2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,364 +0,0 @@
1
- $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
-
3
- require 'rspec'
4
- require 'logger'
5
- require 'active_record/connection_adapters/mysql_master_slave_adapter'
6
-
7
- module ActiveRecord
8
- class Base
9
- cattr_accessor :master_mock, :slave_mock
10
- def self.mysql_connection(config)
11
- config[:database] == 'slave' ? slave_mock : master_mock
12
- end
13
- end
14
- end
15
-
16
- describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
17
- let(:database_setup) do
18
- {
19
- :adapter => 'master_slave',
20
- :username => 'root',
21
- :database => 'slave',
22
- :connection_adapter => 'mysql',
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 supports_prepared_statements?
78
- ActiveRecord::ConnectionAdapters::MysqlAdapter.instance_methods.map(&:to_sym).include?(:exec_without_stmt)
79
- end
80
-
81
- def select_method
82
- supports_prepared_statements? ? :exec_without_stmt : :select_one
83
- end
84
-
85
- def should_report_clock(pos, connection, log_file, log_pos, sql)
86
- pos = Array(pos)
87
- values = pos.map { |p| { log_file => '', log_pos => p } }
88
- values.map! { |result| [ result ] } if supports_prepared_statements?
89
-
90
- connection.
91
- should_receive(select_method).exactly(pos.length).times.
92
- with(sql).
93
- and_return(*values)
94
- end
95
-
96
- def slave_should_report_clock(pos)
97
- should_report_clock(pos, slave_connection, 'Relay_Master_Log_File', 'Exec_Master_Log_Pos', 'SHOW SLAVE STATUS')
98
- end
99
-
100
- def master_should_report_clock(pos)
101
- should_report_clock(pos, master_connection, 'File', 'Position', 'SHOW MASTER STATUS')
102
- end
103
-
104
- SelectMethods.each do |method|
105
- it "should send the method '#{method}' to the slave if nil is given" do
106
- slave_should_report_clock(0)
107
- slave_connection.should_receive(method).with('testing').and_return(true)
108
- new_clock = ActiveRecord::Base.with_consistency(nil) do
109
- adapter_connection.send(method, 'testing')
110
- end
111
- new_clock.should be_a(Clock)
112
- new_clock.should equal(zero)
113
- end
114
-
115
- it "should send the method '#{method}' to the slave if clock.zero is given" do
116
- slave_should_report_clock(0)
117
- slave_connection.should_receive(method).with('testing').and_return(true)
118
- old_clock = zero
119
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
120
- adapter_connection.send(method, 'testing')
121
- end
122
- new_clock.should be_a(Clock)
123
- new_clock.should equal(old_clock)
124
- end
125
-
126
- it "should send the method '#{method}' to the master if slave hasn't cought up to required clock yet" do
127
- slave_should_report_clock(0)
128
- master_connection.should_receive(method).with('testing').and_return(true)
129
- old_clock = master_position(1)
130
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
131
- adapter_connection.send(method, 'testing' )
132
- end
133
- new_clock.should be_a(Clock)
134
- new_clock.should equal(old_clock)
135
- end
136
-
137
- it "should send the method '#{method}' to the master connection if there are open transactions" do
138
- master_connection.stub!(:open_transactions).and_return(1)
139
- master_connection.should_receive(method).with('testing').and_return(true)
140
- old_clock = zero
141
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
142
- adapter_connection.send(method, 'testing')
143
- end
144
- new_clock.should be_a(Clock)
145
- new_clock.should equal(zero)
146
- end
147
-
148
- it "should send the method '#{method}' to the master after a write operation" do
149
- slave_should_report_clock(0)
150
- master_should_report_clock(2)
151
- slave_connection.should_receive(method).with('testing').and_return(true)
152
- master_connection.should_receive(:update).with('testing').and_return(true)
153
- master_connection.should_receive(method).with('testing').and_return(true)
154
- old_clock = zero
155
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
156
- adapter_connection.send(method, 'testing') # slave
157
- adapter_connection.send(:update, 'testing') # master
158
- adapter_connection.send(method, 'testing') # master
159
- end
160
- new_clock.should be_a(Clock)
161
- new_clock.should > old_clock
162
- end
163
- end
164
-
165
- it "should update the clock after a transaction" do
166
- slave_should_report_clock(0)
167
- master_should_report_clock([0, 1, 1])
168
-
169
- slave_connection.
170
- should_receive(:select_all).exactly(1).times.with('testing').
171
- and_return(true)
172
-
173
- master_connection.
174
- should_receive(:update).exactly(3).times.with('testing').
175
- and_return(true)
176
- master_connection.
177
- should_receive(:select_all).exactly(5).times.with('testing').
178
- and_return(true)
179
- %w(begin_db_transaction
180
- commit_db_transaction
181
- increment_open_transactions
182
- decrement_open_transactions
183
- outside_transaction?).each do |txstmt|
184
- master_connection.should_receive(txstmt).exactly(1).times
185
- end
186
-
187
- master_connection.
188
- should_receive('open_transactions').exactly(13).times.
189
- and_return(
190
- # adapter: with_consistency, select_all, update, select_all
191
- 0, 0, 0, 0,
192
- # connection: transaction
193
- 0,
194
- # adapter: select_all, update, select_all, commit_db_transaction
195
- 1, 1, 1, 0,
196
- # connection: transaction (ensure)
197
- 0,
198
- # adapter: select_all, update, select_all
199
- 0, 0, 0
200
- )
201
-
202
- old_clock = zero
203
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
204
- adapter_connection.send(:select_all, 'testing') # slave s=0 m=0
205
- adapter_connection.send(:update, 'testing') # master s=0 m=1
206
- adapter_connection.send(:select_all, 'testing') # master s=0 m=1
207
-
208
- ActiveRecord::Base.transaction do
209
- adapter_connection.send(:select_all, 'testing') # master s=0 m=1
210
- adapter_connection.send(:update, 'testing') # master s=0 m=1
211
- adapter_connection.send(:select_all, 'testing') # master s=0 m=1
212
- end
213
-
214
- adapter_connection.send(:select_all, 'testing') # master s=0 m=2
215
- adapter_connection.send(:update, 'testing') # master s=0 m=3
216
- adapter_connection.send(:select_all, 'testing') # master s=0 m=3
217
- end
218
-
219
- new_clock.should > old_clock
220
- end
221
-
222
- context "with nested with_consistency" do
223
- it "should return the same clock if not writing and no lag" do
224
- slave_should_report_clock(0)
225
- slave_connection.
226
- should_receive(:select_one).exactly(3).times.with('testing').
227
- and_return(true)
228
-
229
- old_clock = zero
230
- new_clock = ActiveRecord::Base.with_consistency(old_clock) do
231
- adapter_connection.send(:select_one, 'testing')
232
- ActiveRecord::Base.with_consistency(old_clock) do
233
- adapter_connection.send(:select_one, 'testing')
234
- end
235
- adapter_connection.send(:select_one, 'testing')
236
- end
237
- new_clock.should equal(old_clock)
238
- end
239
-
240
- it "requesting a newer clock should return a new clock" do
241
- adapter_connection.
242
- should_receive('slave_consistent?').exactly(2).times.
243
- and_return(true, false)
244
- slave_connection.
245
- should_receive(:select_all).exactly(2).times.with('testing').
246
- and_return(true)
247
- master_connection.
248
- should_receive(:select_all).exactly(1).times.with('testing').
249
- and_return(true)
250
-
251
- start_clock = zero
252
- inner_clock = zero
253
- outer_clock = ActiveRecord::Base.with_consistency(start_clock) do
254
- adapter_connection.send(:select_all, 'testing') # slave
255
- inner_clock = ActiveRecord::Base.with_consistency(master_position(1)) do
256
- adapter_connection.send(:select_all, 'testing') # master
257
- end
258
- adapter_connection.send(:select_all, 'testing') # slave
259
- end
260
-
261
- start_clock.should equal(outer_clock)
262
- inner_clock.should > start_clock
263
- end
264
- end
265
-
266
- it "should do the right thing when nested inside with_master" do
267
- slave_should_report_clock(0)
268
- slave_connection.should_receive(:select_all).exactly(1).times.with('testing').and_return(true)
269
- master_connection.should_receive(:select_all).exactly(2).times.with('testing').and_return(true)
270
- ActiveRecord::Base.with_master do
271
- adapter_connection.send(:select_all, 'testing') # master
272
- ActiveRecord::Base.with_consistency(zero) do
273
- adapter_connection.send(:select_all, 'testing') # slave
274
- end
275
- adapter_connection.send(:select_all, 'testing') # master
276
- end
277
- end
278
-
279
- it "should do the right thing when nested inside with_slave" do
280
- slave_should_report_clock(0)
281
- slave_connection.should_receive(:select_all).exactly(3).times.with('testing').and_return(true)
282
- ActiveRecord::Base.with_slave do
283
- adapter_connection.send(:select_all, 'testing') # slave
284
- ActiveRecord::Base.with_consistency(zero) do
285
- adapter_connection.send(:select_all, 'testing') # slave
286
- end
287
- adapter_connection.send(:select_all, 'testing') # slave
288
- end
289
- end
290
-
291
- it "should do the right thing when wrapping with_master" do
292
- slave_should_report_clock(0)
293
- slave_connection.should_receive(:select_all).exactly(2).times.with('testing').and_return(true)
294
- master_connection.should_receive(:select_all).exactly(1).times.with('testing').and_return(true)
295
- ActiveRecord::Base.with_consistency(zero) do
296
- adapter_connection.send(:select_all, 'testing') # slave
297
- ActiveRecord::Base.with_master do
298
- adapter_connection.send(:select_all, 'testing') # master
299
- end
300
- adapter_connection.send(:select_all, 'testing') # slave
301
- end
302
- end
303
-
304
- it "should do the right thing when wrapping with_slave" do
305
- slave_should_report_clock(0)
306
- slave_connection.should_receive(:select_all).exactly(1).times.with('testing').and_return(true)
307
- master_connection.should_receive(:select_all).exactly(2).times.with('testing').and_return(true)
308
- ActiveRecord::Base.with_consistency(master_position(1)) do
309
- adapter_connection.send(:select_all, 'testing') # master
310
- ActiveRecord::Base.with_slave do
311
- adapter_connection.send(:select_all, 'testing') # slave
312
- end
313
- adapter_connection.send(:select_all, 'testing') # master
314
- end
315
- end
316
-
317
- it "should accept clock as string" do
318
- slave_should_report_clock(0)
319
- slave_connection.should_receive(:select_all).with('testing')
320
-
321
- ActiveRecord::Base.with_consistency("@0") do
322
- adapter_connection.send(:select_all, 'testing')
323
- end
324
- end
325
- end # /with_consistency
326
-
327
- describe "connection error detection" do
328
- {
329
- Mysql::Error::CR_CONNECTION_ERROR => "query: not connected",
330
- Mysql::Error::CR_CONN_HOST_ERROR => "Can't connect to MySQL server on 'localhost' (3306)",
331
- Mysql::Error::CR_SERVER_GONE_ERROR => "MySQL server has gone away",
332
- Mysql::Error::CR_SERVER_LOST => "Lost connection to MySQL server during query",
333
- }.each do |errno, description|
334
- it "raises MasterUnavailable for '#{description}' during query execution" do
335
- master_connection.stub_chain(:raw_connection, :errno).and_return(errno)
336
- master_connection.should_receive(:insert).and_raise(ActiveRecord::StatementInvalid.new("Mysql::Error: #{description}: INSERT 42"))
337
-
338
- expect do
339
- adapter_connection.insert("INSERT 42")
340
- end.to raise_error(ActiveRecord::MasterUnavailable)
341
- end
342
-
343
- it "doesn't raise anything for '#{description}' during connection" do
344
- error = Mysql::Error.new(description)
345
- error.stub(:errno).and_return(errno)
346
- ActiveRecord::Base.should_receive(:master_mock).and_raise(error)
347
-
348
- expect do
349
- ActiveRecord::Base.connection_handler.clear_all_connections!
350
- ActiveRecord::Base.connection
351
- end.to_not raise_error
352
- end
353
- end
354
-
355
- it "raises StatementInvalid for other errors" do
356
- master_connection.stub_chain(:raw_connection, :errno).and_return(Mysql::Error::ER_QUERY_INTERRUPTED)
357
- master_connection.should_receive(:insert).and_raise(ActiveRecord::StatementInvalid.new("Mysql::Error: Query execution was interrupted: INSERT 42"))
358
-
359
- expect do
360
- adapter_connection.insert("INSERT 42")
361
- end.to raise_error(ActiveRecord::StatementInvalid)
362
- end
363
- end
364
- end