master_slave_adapter 0.2.0 → 1.0.0.beta1

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.
@@ -0,0 +1,320 @@
1
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'rspec'
4
+
5
+ class Mysql
6
+ class Error
7
+ CR_CONNECTION_ERROR = 1
8
+ CR_CONN_HOST_ERROR = 2
9
+ CR_SERVER_GONE_ERROR = 4
10
+ CR_SERVER_LOST = 8
11
+ end
12
+ end
13
+
14
+ require 'active_record/connection_adapters/mysql_master_slave_adapter'
15
+
16
+ ActiveRecord::Base.logger =
17
+ Logger.new($stdout).tap { |l| l.level = Logger::DEBUG }
18
+
19
+ module ActiveRecord
20
+ class Base
21
+ cattr_accessor :master_mock, :slave_mock
22
+ def self.mysql_connection(config)
23
+ config[:database] == 'slave' ? slave_mock : master_mock
24
+ end
25
+ end
26
+ end
27
+
28
+ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
29
+ let(:default_database_setup) do
30
+ {
31
+ :adapter => 'master_slave',
32
+ :username => 'root',
33
+ :database => 'slave',
34
+ :connection_adapter => 'mysql',
35
+ :master => { :username => 'root', :database => 'master' },
36
+ :slaves => [{ :database => 'slave' }],
37
+ }
38
+ end
39
+
40
+ let(:database_setup) { default_database_setup }
41
+
42
+ let(:mocked_methods) do
43
+ {
44
+ :reconnect! => true,
45
+ :disconnect! => true,
46
+ :active? => true,
47
+ }
48
+ end
49
+
50
+ let!(:master_connection) do
51
+ mock(
52
+ 'master connection',
53
+ mocked_methods.merge(:open_transactions => 0)
54
+ ).tap do |conn|
55
+ conn.stub!(:uncached).and_yield
56
+ ActiveRecord::Base.master_mock = conn
57
+ end
58
+ end
59
+
60
+ let!(:slave_connection) do
61
+ mock('slave connection', mocked_methods).tap do |conn|
62
+ conn.stub!(:uncached).and_yield
63
+ ActiveRecord::Base.slave_mock = conn
64
+ end
65
+ end
66
+
67
+ def adapter_connection
68
+ ActiveRecord::Base.connection
69
+ end
70
+
71
+ SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ]
72
+ Clock = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock
73
+
74
+ before do
75
+ ActiveRecord::Base.establish_connection(database_setup)
76
+ end
77
+
78
+ after do
79
+ ActiveRecord::Base.connection_handler.clear_all_connections!
80
+ end
81
+
82
+ describe 'consistency' do
83
+ def zero
84
+ Clock.zero
85
+ end
86
+
87
+ def master_position(pos)
88
+ Clock.new('', pos)
89
+ end
90
+
91
+ def slave_should_report_clock(pos)
92
+ pos = Array(pos)
93
+ values = pos.map { |p| { 'Relay_Master_Log_File' => '', 'Exec_Master_Log_Pos' => p } }
94
+ slave_connection.
95
+ should_receive('select_one').exactly(pos.length).with('SHOW SLAVE STATUS').
96
+ and_return(*values)
97
+ end
98
+
99
+ def master_should_report_clock(pos)
100
+ pos = Array(pos)
101
+ values = pos.map { |p| { 'File' => '', 'Position' => p } }
102
+ master_connection.
103
+ should_receive('select_one').exactly(pos.length).with('SHOW MASTER STATUS').
104
+ and_return(*values)
105
+ end
106
+
107
+ SelectMethods.each do |method|
108
+ it "should send the method '#{method}' to the slave if nil is given" do
109
+ slave_should_report_clock(0)
110
+ slave_connection.should_receive(method).with('testing').and_return(true)
111
+ new_clock = ActiveRecord::Base.with_consistency(nil) do
112
+ adapter_connection.send(method, 'testing')
113
+ end
114
+ new_clock.should be_a(Clock)
115
+ new_clock.should equal(zero)
116
+ end
117
+
118
+ it "should send the method '#{method}' to the slave if clock.zero is given" do
119
+ slave_should_report_clock(0)
120
+ slave_connection.should_receive(method).with('testing').and_return(true)
121
+ old_clock = zero
122
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
123
+ adapter_connection.send(method, 'testing')
124
+ end
125
+ new_clock.should be_a(Clock)
126
+ new_clock.should equal(old_clock)
127
+ end
128
+
129
+ it "should send the method '#{method}' to the master if slave hasn't cought up to required clock yet" do
130
+ slave_should_report_clock(0)
131
+ master_connection.should_receive(method).with('testing').and_return(true)
132
+ old_clock = master_position(1)
133
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
134
+ adapter_connection.send(method, 'testing' )
135
+ end
136
+ new_clock.should be_a(Clock)
137
+ new_clock.should equal(old_clock)
138
+ end
139
+
140
+ it "should send the method '#{method}' to the master connection if there are open transactions" do
141
+ master_connection.stub!(:open_transactions).and_return(1)
142
+ master_connection.should_receive(method).with('testing').and_return(true)
143
+ old_clock = zero
144
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
145
+ adapter_connection.send(method, 'testing')
146
+ end
147
+ new_clock.should be_a(Clock)
148
+ new_clock.should equal(zero)
149
+ end
150
+
151
+ it "should send the method '#{method}' to the master after a write operation" do
152
+ slave_should_report_clock(0)
153
+ master_should_report_clock(2)
154
+ slave_connection.should_receive(method).with('testing').and_return(true)
155
+ master_connection.should_receive('update').with('testing').and_return(true)
156
+ master_connection.should_receive(method).with('testing').and_return(true)
157
+ old_clock = zero
158
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
159
+ adapter_connection.send(method, 'testing') # slave
160
+ adapter_connection.send('update', 'testing') # master
161
+ adapter_connection.send(method, 'testing') # master
162
+ end
163
+ new_clock.should be_a(Clock)
164
+ new_clock.should > old_clock
165
+ end
166
+ end
167
+
168
+ it "should update the clock after a transaction" do
169
+ slave_should_report_clock(0)
170
+ master_should_report_clock([0, 1, 1])
171
+
172
+ slave_connection.
173
+ should_receive('select_all').exactly(1).times.with('testing').
174
+ and_return(true)
175
+
176
+ master_connection.
177
+ should_receive('update').exactly(3).times.with('testing').
178
+ and_return(true)
179
+ master_connection.
180
+ should_receive('select_all').exactly(5).times.with('testing').
181
+ and_return(true)
182
+ %w(begin_db_transaction
183
+ commit_db_transaction
184
+ increment_open_transactions
185
+ decrement_open_transactions
186
+ outside_transaction?).each do |txstmt|
187
+ master_connection.should_receive(txstmt).exactly(1).times
188
+ end
189
+
190
+ master_connection.
191
+ should_receive('open_transactions').exactly(13).times.
192
+ and_return(
193
+ # adapter: with_consistency, select_all, update, select_all
194
+ 0, 0, 0, 0,
195
+ # connection: transaction
196
+ 0,
197
+ # adapter: select_all, update, select_all, commit_db_transaction
198
+ 1, 1, 1, 0,
199
+ # connection: transaction (ensure)
200
+ 0,
201
+ # adapter: select_all, update, select_all
202
+ 0, 0, 0
203
+ )
204
+
205
+ old_clock = zero
206
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
207
+ adapter_connection.send('select_all', 'testing') # slave s=0 m=0
208
+ adapter_connection.send('update', 'testing') # master s=0 m=1
209
+ adapter_connection.send('select_all', 'testing') # master s=0 m=1
210
+
211
+ ActiveRecord::Base.transaction do
212
+ adapter_connection.send('select_all', 'testing') # master s=0 m=1
213
+ adapter_connection.send('update', 'testing') # master s=0 m=1
214
+ adapter_connection.send('select_all', 'testing') # master s=0 m=1
215
+ end
216
+
217
+ adapter_connection.send('select_all', 'testing') # master s=0 m=2
218
+ adapter_connection.send('update', 'testing') # master s=0 m=3
219
+ adapter_connection.send('select_all', 'testing') # master s=0 m=3
220
+ end
221
+
222
+ new_clock.should > old_clock
223
+ end
224
+
225
+ context "with nested with_consistency" do
226
+ it "should return the same clock if not writing and no lag" do
227
+ slave_should_report_clock(0) # note: tests memoizing slave clock
228
+ slave_connection.
229
+ should_receive('select_one').exactly(3).times.with('testing').
230
+ and_return(true)
231
+
232
+ old_clock = zero
233
+ new_clock = ActiveRecord::Base.with_consistency(old_clock) do
234
+ adapter_connection.send('select_one', 'testing')
235
+ ActiveRecord::Base.with_consistency(old_clock) do
236
+ adapter_connection.send('select_one', 'testing')
237
+ end
238
+ adapter_connection.send('select_one', 'testing')
239
+ end
240
+ new_clock.should equal(old_clock)
241
+ end
242
+
243
+ it "requesting a newer clock should return a new clock" do
244
+ adapter_connection.
245
+ should_receive('slave_consistent?').exactly(2).times.
246
+ and_return(true, false)
247
+ slave_connection.
248
+ should_receive('select_all').exactly(2).times.with('testing').
249
+ and_return(true)
250
+ master_connection.
251
+ should_receive('select_all').exactly(1).times.with('testing').
252
+ and_return(true)
253
+
254
+ start_clock = zero
255
+ inner_clock = zero
256
+ outer_clock = ActiveRecord::Base.with_consistency(start_clock) do
257
+ adapter_connection.send('select_all', 'testing') # slave
258
+ inner_clock = ActiveRecord::Base.with_consistency(master_position(1)) do
259
+ adapter_connection.send('select_all', 'testing') # master
260
+ end
261
+ adapter_connection.send('select_all', 'testing') # slave
262
+ end
263
+
264
+ start_clock.should equal(outer_clock)
265
+ inner_clock.should > start_clock
266
+ end
267
+ end
268
+
269
+ it "should do the right thing when nested inside with_master" do
270
+ slave_should_report_clock(0)
271
+ slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
272
+ master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
273
+ ActiveRecord::Base.with_master do
274
+ adapter_connection.send('select_all', 'testing') # master
275
+ ActiveRecord::Base.with_consistency(zero) do
276
+ adapter_connection.send('select_all', 'testing') # slave
277
+ end
278
+ adapter_connection.send('select_all', 'testing') # master
279
+ end
280
+ end
281
+
282
+ it "should do the right thing when nested inside with_slave" do
283
+ slave_should_report_clock(0)
284
+ slave_connection.should_receive('select_all').exactly(3).times.with('testing').and_return(true)
285
+ ActiveRecord::Base.with_slave do
286
+ adapter_connection.send('select_all', 'testing') # slave
287
+ ActiveRecord::Base.with_consistency(zero) do
288
+ adapter_connection.send('select_all', 'testing') # slave
289
+ end
290
+ adapter_connection.send('select_all', 'testing') # slave
291
+ end
292
+ end
293
+
294
+ it "should do the right thing when wrapping with_master" do
295
+ slave_should_report_clock(0)
296
+ slave_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
297
+ master_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
298
+ ActiveRecord::Base.with_consistency(zero) do
299
+ adapter_connection.send('select_all', 'testing') # slave
300
+ ActiveRecord::Base.with_master do
301
+ adapter_connection.send('select_all', 'testing') # master
302
+ end
303
+ adapter_connection.send('select_all', 'testing') # slave
304
+ end
305
+ end
306
+
307
+ it "should do the right thing when wrapping with_slave" do
308
+ slave_should_report_clock(0)
309
+ slave_connection.should_receive('select_all').exactly(1).times.with('testing').and_return(true)
310
+ master_connection.should_receive('select_all').exactly(2).times.with('testing').and_return(true)
311
+ ActiveRecord::Base.with_consistency(master_position(1)) do
312
+ adapter_connection.send('select_all', 'testing') # master
313
+ ActiveRecord::Base.with_slave do
314
+ adapter_connection.send('select_all', 'testing') # slave
315
+ end
316
+ adapter_connection.send('select_all', 'testing') # master
317
+ end
318
+ end
319
+ end # /with_consistency
320
+ end
metadata CHANGED
@@ -1,19 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: master_slave_adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
5
- prerelease:
4
+ version: 1.0.0.beta1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Mauricio Linhares
9
9
  - Torsten Curdt
10
10
  - Kim Altintop
11
11
  - Omid Aladini
12
+ - Tiago Loureiro
13
+ - Tobias Schmidt
12
14
  - SoundCloud
13
15
  autorequire:
14
16
  bindir: bin
15
17
  cert_chain: []
16
- date: 2012-05-21 00:00:00.000000000 Z
18
+ date: 2012-05-24 00:00:00.000000000 Z
17
19
  dependencies:
18
20
  - !ruby/object:Gem::Dependency
19
21
  name: rspec
@@ -63,8 +65,8 @@ dependencies:
63
65
  - - ~>
64
66
  - !ruby/object:Gem::Version
65
67
  version: 2.3.9
66
- description: (MySQL) Replication Aware Master/Slave Database Adapter for Rails/ActiveRecord
67
- email: kim@soundcloud.com tcurdt@soundcloud.com omid@soundcloud.com
68
+ description: (MySQL) Replication Aware Master/Slave Database Adapter for ActiveRecord
69
+ email: kim@soundcloud.com tcurdt@soundcloud.com ts@soundcloud
68
70
  executables: []
69
71
  extensions: []
70
72
  extra_rdoc_files: []
@@ -77,12 +79,16 @@ files:
77
79
  - Rakefile
78
80
  - Readme.md
79
81
  - TODO.txt
80
- - VERSION
81
82
  - lib/active_record/connection_adapters/master_slave_adapter.rb
83
+ - lib/active_record/connection_adapters/master_slave_adapter/circuit_breaker.rb
84
+ - lib/active_record/connection_adapters/master_slave_adapter/clock.rb
85
+ - lib/active_record/connection_adapters/master_slave_adapter/version.rb
82
86
  - lib/active_record/connection_adapters/mysql_master_slave_adapter.rb
83
87
  - lib/master_slave_adapter.rb
84
88
  - master_slave_adapter.gemspec
89
+ - spec/circuit_breaker_spec.rb
85
90
  - spec/master_slave_adapter_spec.rb
91
+ - spec/mysql_master_slave_adapter_spec.rb
86
92
  homepage: http://github.com/soundcloud/master_slave_adapter
87
93
  licenses: []
88
94
  post_install_message:
@@ -94,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
100
  requirements:
95
101
  - - ! '>='
96
102
  - !ruby/object:Gem::Version
97
- version: 1.9.2
103
+ version: 1.8.7
98
104
  required_rubygems_version: !ruby/object:Gem::Requirement
99
105
  none: false
100
106
  requirements:
@@ -106,6 +112,8 @@ rubyforge_project:
106
112
  rubygems_version: 1.8.21
107
113
  signing_key:
108
114
  specification_version: 3
109
- summary: Replication Aware Master/Slave Database Adapter for Rails/ActiveRecord
115
+ summary: Replication Aware Master/Slave Database Adapter for ActiveRecord
110
116
  test_files:
117
+ - spec/circuit_breaker_spec.rb
111
118
  - spec/master_slave_adapter_spec.rb
119
+ - spec/mysql_master_slave_adapter_spec.rb
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.2.0