master_slave_adapter 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,9 @@
1
1
  $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
2
 
3
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
-
4
+ require 'logger'
14
5
  require 'active_record/connection_adapters/mysql_master_slave_adapter'
15
6
 
16
- ActiveRecord::Base.logger =
17
- Logger.new($stdout).tap { |l| l.level = Logger::DEBUG }
18
-
19
7
  module ActiveRecord
20
8
  class Base
21
9
  cattr_accessor :master_mock, :slave_mock
@@ -26,7 +14,7 @@ module ActiveRecord
26
14
  end
27
15
 
28
16
  describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
29
- let(:default_database_setup) do
17
+ let(:database_setup) do
30
18
  {
31
19
  :adapter => 'master_slave',
32
20
  :username => 'root',
@@ -37,13 +25,11 @@ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
37
25
  }
38
26
  end
39
27
 
40
- let(:database_setup) { default_database_setup }
41
-
42
28
  let(:mocked_methods) do
43
29
  {
44
- :reconnect! => true,
30
+ :reconnect! => true,
45
31
  :disconnect! => true,
46
- :active? => true,
32
+ :active? => true,
47
33
  }
48
34
  end
49
35
 
@@ -68,8 +54,8 @@ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
68
54
  ActiveRecord::Base.connection
69
55
  end
70
56
 
71
- SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ]
72
- Clock = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock
57
+ SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ] unless defined?(SelectMethods)
58
+ Clock = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock unless defined?(Clock)
73
59
 
74
60
  before do
75
61
  ActiveRecord::Base.establish_connection(database_setup)
@@ -88,20 +74,31 @@ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
88
74
  Clock.new('', pos)
89
75
  end
90
76
 
91
- def slave_should_report_clock(pos)
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)
92
86
  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').
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).
96
93
  and_return(*values)
97
94
  end
98
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
+
99
100
  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)
101
+ should_report_clock(pos, master_connection, 'File', 'Position', 'SHOW MASTER STATUS')
105
102
  end
106
103
 
107
104
  SelectMethods.each do |method|
@@ -152,12 +149,12 @@ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
152
149
  slave_should_report_clock(0)
153
150
  master_should_report_clock(2)
154
151
  slave_connection.should_receive(method).with('testing').and_return(true)
155
- master_connection.should_receive('update').with('testing').and_return(true)
152
+ master_connection.should_receive(:update).with('testing').and_return(true)
156
153
  master_connection.should_receive(method).with('testing').and_return(true)
157
154
  old_clock = zero
158
155
  new_clock = ActiveRecord::Base.with_consistency(old_clock) do
159
156
  adapter_connection.send(method, 'testing') # slave
160
- adapter_connection.send('update', 'testing') # master
157
+ adapter_connection.send(:update, 'testing') # master
161
158
  adapter_connection.send(method, 'testing') # master
162
159
  end
163
160
  new_clock.should be_a(Clock)
@@ -170,14 +167,14 @@ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
170
167
  master_should_report_clock([0, 1, 1])
171
168
 
172
169
  slave_connection.
173
- should_receive('select_all').exactly(1).times.with('testing').
170
+ should_receive(:select_all).exactly(1).times.with('testing').
174
171
  and_return(true)
175
172
 
176
173
  master_connection.
177
- should_receive('update').exactly(3).times.with('testing').
174
+ should_receive(:update).exactly(3).times.with('testing').
178
175
  and_return(true)
179
176
  master_connection.
180
- should_receive('select_all').exactly(5).times.with('testing').
177
+ should_receive(:select_all).exactly(5).times.with('testing').
181
178
  and_return(true)
182
179
  %w(begin_db_transaction
183
180
  commit_db_transaction
@@ -204,19 +201,19 @@ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
204
201
 
205
202
  old_clock = zero
206
203
  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
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
210
207
 
211
208
  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
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
215
212
  end
216
213
 
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
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
220
217
  end
221
218
 
222
219
  new_clock.should > old_clock
@@ -224,18 +221,18 @@ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
224
221
 
225
222
  context "with nested with_consistency" do
226
223
  it "should return the same clock if not writing and no lag" do
227
- slave_should_report_clock(0) # note: tests memoizing slave clock
224
+ slave_should_report_clock(0)
228
225
  slave_connection.
229
- should_receive('select_one').exactly(3).times.with('testing').
226
+ should_receive(:select_one).exactly(3).times.with('testing').
230
227
  and_return(true)
231
228
 
232
229
  old_clock = zero
233
230
  new_clock = ActiveRecord::Base.with_consistency(old_clock) do
234
- adapter_connection.send('select_one', 'testing')
231
+ adapter_connection.send(:select_one, 'testing')
235
232
  ActiveRecord::Base.with_consistency(old_clock) do
236
- adapter_connection.send('select_one', 'testing')
233
+ adapter_connection.send(:select_one, 'testing')
237
234
  end
238
- adapter_connection.send('select_one', 'testing')
235
+ adapter_connection.send(:select_one, 'testing')
239
236
  end
240
237
  new_clock.should equal(old_clock)
241
238
  end
@@ -245,20 +242,20 @@ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
245
242
  should_receive('slave_consistent?').exactly(2).times.
246
243
  and_return(true, false)
247
244
  slave_connection.
248
- should_receive('select_all').exactly(2).times.with('testing').
245
+ should_receive(:select_all).exactly(2).times.with('testing').
249
246
  and_return(true)
250
247
  master_connection.
251
- should_receive('select_all').exactly(1).times.with('testing').
248
+ should_receive(:select_all).exactly(1).times.with('testing').
252
249
  and_return(true)
253
250
 
254
251
  start_clock = zero
255
252
  inner_clock = zero
256
253
  outer_clock = ActiveRecord::Base.with_consistency(start_clock) do
257
- adapter_connection.send('select_all', 'testing') # slave
254
+ adapter_connection.send(:select_all, 'testing') # slave
258
255
  inner_clock = ActiveRecord::Base.with_consistency(master_position(1)) do
259
- adapter_connection.send('select_all', 'testing') # master
256
+ adapter_connection.send(:select_all, 'testing') # master
260
257
  end
261
- adapter_connection.send('select_all', 'testing') # slave
258
+ adapter_connection.send(:select_all, 'testing') # slave
262
259
  end
263
260
 
264
261
  start_clock.should equal(outer_clock)
@@ -268,53 +265,100 @@ describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
268
265
 
269
266
  it "should do the right thing when nested inside with_master" do
270
267
  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)
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)
273
270
  ActiveRecord::Base.with_master do
274
- adapter_connection.send('select_all', 'testing') # master
271
+ adapter_connection.send(:select_all, 'testing') # master
275
272
  ActiveRecord::Base.with_consistency(zero) do
276
- adapter_connection.send('select_all', 'testing') # slave
273
+ adapter_connection.send(:select_all, 'testing') # slave
277
274
  end
278
- adapter_connection.send('select_all', 'testing') # master
275
+ adapter_connection.send(:select_all, 'testing') # master
279
276
  end
280
277
  end
281
278
 
282
279
  it "should do the right thing when nested inside with_slave" do
283
280
  slave_should_report_clock(0)
284
- slave_connection.should_receive('select_all').exactly(3).times.with('testing').and_return(true)
281
+ slave_connection.should_receive(:select_all).exactly(3).times.with('testing').and_return(true)
285
282
  ActiveRecord::Base.with_slave do
286
- adapter_connection.send('select_all', 'testing') # slave
283
+ adapter_connection.send(:select_all, 'testing') # slave
287
284
  ActiveRecord::Base.with_consistency(zero) do
288
- adapter_connection.send('select_all', 'testing') # slave
285
+ adapter_connection.send(:select_all, 'testing') # slave
289
286
  end
290
- adapter_connection.send('select_all', 'testing') # slave
287
+ adapter_connection.send(:select_all, 'testing') # slave
291
288
  end
292
289
  end
293
290
 
294
291
  it "should do the right thing when wrapping with_master" do
295
292
  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)
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)
298
295
  ActiveRecord::Base.with_consistency(zero) do
299
- adapter_connection.send('select_all', 'testing') # slave
296
+ adapter_connection.send(:select_all, 'testing') # slave
300
297
  ActiveRecord::Base.with_master do
301
- adapter_connection.send('select_all', 'testing') # master
298
+ adapter_connection.send(:select_all, 'testing') # master
302
299
  end
303
- adapter_connection.send('select_all', 'testing') # slave
300
+ adapter_connection.send(:select_all, 'testing') # slave
304
301
  end
305
302
  end
306
303
 
307
304
  it "should do the right thing when wrapping with_slave" do
308
305
  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)
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)
311
308
  ActiveRecord::Base.with_consistency(master_position(1)) do
312
- adapter_connection.send('select_all', 'testing') # master
309
+ adapter_connection.send(:select_all, 'testing') # master
313
310
  ActiveRecord::Base.with_slave do
314
- adapter_connection.send('select_all', 'testing') # slave
311
+ adapter_connection.send(:select_all, 'testing') # slave
315
312
  end
316
- adapter_connection.send('select_all', 'testing') # master
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')
317
323
  end
318
324
  end
319
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
320
364
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: master_slave_adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta1
4
+ version: 1.0.0.beta2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -15,24 +15,30 @@ authors:
15
15
  autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
- date: 2012-05-24 00:00:00.000000000 Z
18
+ date: 2012-06-20 00:00:00.000000000 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: rspec
21
+ name: activerecord
22
22
  requirement: !ruby/object:Gem::Requirement
23
23
  none: false
24
24
  requirements:
25
25
  - - ! '>='
26
26
  - !ruby/object:Gem::Version
27
- version: '0'
28
- type: :development
27
+ version: 2.3.9
28
+ - - <=
29
+ - !ruby/object:Gem::Version
30
+ version: '4.0'
31
+ type: :runtime
29
32
  prerelease: false
30
33
  version_requirements: !ruby/object:Gem::Requirement
31
34
  none: false
32
35
  requirements:
33
36
  - - ! '>='
34
37
  - !ruby/object:Gem::Version
35
- version: '0'
38
+ version: 2.3.9
39
+ - - <=
40
+ - !ruby/object:Gem::Version
41
+ version: '4.0'
36
42
  - !ruby/object:Gem::Dependency
37
43
  name: rake
38
44
  requirement: !ruby/object:Gem::Requirement
@@ -50,21 +56,21 @@ dependencies:
50
56
  - !ruby/object:Gem::Version
51
57
  version: '0'
52
58
  - !ruby/object:Gem::Dependency
53
- name: activerecord
59
+ name: rspec
54
60
  requirement: !ruby/object:Gem::Requirement
55
61
  none: false
56
62
  requirements:
57
- - - ~>
63
+ - - ! '>='
58
64
  - !ruby/object:Gem::Version
59
- version: 2.3.9
60
- type: :runtime
65
+ version: '0'
66
+ type: :development
61
67
  prerelease: false
62
68
  version_requirements: !ruby/object:Gem::Requirement
63
69
  none: false
64
70
  requirements:
65
- - - ~>
71
+ - - ! '>='
66
72
  - !ruby/object:Gem::Version
67
- version: 2.3.9
73
+ version: '0'
68
74
  description: (MySQL) Replication Aware Master/Slave Database Adapter for ActiveRecord
69
75
  email: kim@soundcloud.com tcurdt@soundcloud.com ts@soundcloud
70
76
  executables: []
@@ -72,22 +78,32 @@ extensions: []
72
78
  extra_rdoc_files: []
73
79
  files:
74
80
  - .gitignore
81
+ - .rspec
75
82
  - .travis.yml
76
83
  - CHANGELOG.md
77
- - Gemfile
78
84
  - LICENSE
79
85
  - Rakefile
80
86
  - Readme.md
81
- - TODO.txt
82
87
  - lib/active_record/connection_adapters/master_slave_adapter.rb
83
88
  - lib/active_record/connection_adapters/master_slave_adapter/circuit_breaker.rb
84
89
  - lib/active_record/connection_adapters/master_slave_adapter/clock.rb
90
+ - lib/active_record/connection_adapters/master_slave_adapter/shared_mysql_adapter_behavior.rb
85
91
  - lib/active_record/connection_adapters/master_slave_adapter/version.rb
92
+ - lib/active_record/connection_adapters/mysql2_master_slave_adapter.rb
86
93
  - lib/active_record/connection_adapters/mysql_master_slave_adapter.rb
87
94
  - lib/master_slave_adapter.rb
88
95
  - master_slave_adapter.gemspec
96
+ - spec/all.sh
89
97
  - spec/circuit_breaker_spec.rb
98
+ - spec/gemfiles/activerecord2.3
99
+ - spec/gemfiles/activerecord3.0
100
+ - spec/gemfiles/activerecord3.2
101
+ - spec/integration/helpers/mysql_helper.rb
102
+ - spec/integration/helpers/shared_mysql_examples.rb
103
+ - spec/integration/mysql2_master_slave_adapter_spec.rb
104
+ - spec/integration/mysql_master_slave_adapter_spec.rb
90
105
  - spec/master_slave_adapter_spec.rb
106
+ - spec/mysql2_master_slave_adapter_spec.rb
91
107
  - spec/mysql_master_slave_adapter_spec.rb
92
108
  homepage: http://github.com/soundcloud/master_slave_adapter
93
109
  licenses: []
@@ -109,11 +125,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
125
  version: 1.3.7
110
126
  requirements: []
111
127
  rubyforge_project:
112
- rubygems_version: 1.8.21
128
+ rubygems_version: 1.8.24
113
129
  signing_key:
114
130
  specification_version: 3
115
131
  summary: Replication Aware Master/Slave Database Adapter for ActiveRecord
116
132
  test_files:
133
+ - spec/all.sh
117
134
  - spec/circuit_breaker_spec.rb
135
+ - spec/gemfiles/activerecord2.3
136
+ - spec/gemfiles/activerecord3.0
137
+ - spec/gemfiles/activerecord3.2
138
+ - spec/integration/helpers/mysql_helper.rb
139
+ - spec/integration/helpers/shared_mysql_examples.rb
140
+ - spec/integration/mysql2_master_slave_adapter_spec.rb
141
+ - spec/integration/mysql_master_slave_adapter_spec.rb
118
142
  - spec/master_slave_adapter_spec.rb
143
+ - spec/mysql2_master_slave_adapter_spec.rb
119
144
  - spec/mysql_master_slave_adapter_spec.rb
data/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- gemspec
data/TODO.txt DELETED
@@ -1,18 +0,0 @@
1
- Read only mode
2
- --------------
3
-
4
- - Write tests
5
- - Clock.parse
6
- - connection_error (integration test)
7
- - integration tests
8
- - with_consistency accepts string clock
9
- - raise MasterUnavailable in all cases
10
- - connection stack usage
11
-
12
- - Check
13
- - thread safety
14
- - AR reconnect behavior / connection check
15
- - replication in other databases
16
-
17
- - restructure MasterSlaveAdapter with proper namespaces
18
- - make clock private