master_slave_adapter 1.0.0.beta2 → 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.
@@ -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