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.
- data/Rakefile +1 -1
- data/Readme.md +10 -0
- data/lib/active_record/connection_adapters/master_slave_adapter/version.rb +1 -1
- data/master_slave_adapter.gemspec +2 -2
- data/spec/all.sh +1 -1
- data/spec/{circuit_breaker_spec.rb → common/circuit_breaker_spec.rb} +1 -1
- data/spec/{master_slave_adapter_spec.rb → common/master_slave_adapter_spec.rb} +7 -58
- data/spec/common/mysql2_master_slave_adapter_spec.rb +68 -0
- data/spec/common/mysql_master_slave_adapter_spec.rb +62 -0
- data/spec/common/support/connection_setup_helper.rb +56 -0
- data/spec/common/support/mysql_consistency_examples.rb +263 -0
- data/spec/integration/mysql2_master_slave_adapter_spec.rb +1 -1
- data/spec/integration/mysql_master_slave_adapter_spec.rb +1 -1
- data/spec/integration/{helpers/mysql_helper.rb → support/mysql_setup_helper.rb} +1 -1
- data/spec/integration/{helpers → support}/shared_mysql_examples.rb +3 -3
- metadata +22 -18
- data/spec/mysql2_master_slave_adapter_spec.rb +0 -372
- data/spec/mysql_master_slave_adapter_spec.rb +0 -364
data/Rakefile
CHANGED
@@ -31,7 +31,7 @@ task :spec => ['spec:common', 'spec:integration']
|
|
31
31
|
namespace :spec do
|
32
32
|
desc 'Run common specs'
|
33
33
|
MasterSlaveAdapterRSpecTask.new(:common) do |task|
|
34
|
-
task.pattern = './spec/*_spec.rb'
|
34
|
+
task.pattern = './spec/common/*_spec.rb'
|
35
35
|
task.exclude = /mysql2/ unless mysql2_adapter_available?
|
36
36
|
task.verbose = false
|
37
37
|
end
|
data/Readme.md
CHANGED
@@ -180,6 +180,16 @@ development:
|
|
180
180
|
- host: slave02
|
181
181
|
```
|
182
182
|
|
183
|
+
## Testing
|
184
|
+
|
185
|
+
You can execute all tests against your current ruby version via:
|
186
|
+
|
187
|
+
rake spec
|
188
|
+
|
189
|
+
In case you have `rvm` installed, you can test against 1.8.7, 1.9.2 and 1.9.3 as well as ActiveRecord 2 and 3 via:
|
190
|
+
|
191
|
+
bash spec/all.sh
|
192
|
+
|
183
193
|
## Credits
|
184
194
|
|
185
195
|
* Maurício Lenhares - _original master_slave_adapter plugin_
|
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = [ 'Mauricio Linhares', 'Torsten Curdt', 'Kim Altintop', 'Omid Aladini', 'Tiago Loureiro', 'Tobias Schmidt', 'SoundCloud' ]
|
10
|
-
s.email = %q{
|
10
|
+
s.email = %q{tiago@soundcloud.com ts@soundcloud.com}
|
11
11
|
s.homepage = 'http://github.com/soundcloud/master_slave_adapter'
|
12
12
|
s.summary = %q{Replication Aware Master/Slave Database Adapter for ActiveRecord}
|
13
13
|
s.description = %q{(MySQL) Replication Aware Master/Slave Database Adapter for ActiveRecord}
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.required_ruby_version = '>= 1.8.7'
|
21
21
|
s.required_rubygems_version = '>= 1.3.7'
|
22
22
|
|
23
|
-
s.add_dependency 'activerecord', ['>= 2.3.9', '
|
23
|
+
s.add_dependency 'activerecord', ['>= 2.3.9', '< 4.0']
|
24
24
|
|
25
25
|
s.add_development_dependency 'rake'
|
26
26
|
s.add_development_dependency 'rspec'
|
data/spec/all.sh
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
1
|
+
$: << File.expand_path(File.join(File.dirname( __FILE__ ), '..', '..', 'lib'))
|
2
2
|
|
3
3
|
require 'rspec'
|
4
|
-
require '
|
5
|
-
require 'active_record/connection_adapters/master_slave_adapter'
|
4
|
+
require 'common/support/connection_setup_helper'
|
6
5
|
|
7
6
|
module ActiveRecord
|
8
7
|
class Base
|
9
8
|
cattr_accessor :master_mock, :slave_mock
|
9
|
+
|
10
10
|
def self.test_connection(config)
|
11
11
|
config[:database] == 'slave' ? slave_mock : master_mock
|
12
12
|
end
|
@@ -27,65 +27,14 @@ module ActiveRecord
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def connection_error?(exception)
|
30
|
-
true
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
35
|
describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
|
37
|
-
|
38
|
-
|
39
|
-
:adapter => 'master_slave',
|
40
|
-
:username => 'root',
|
41
|
-
:database => 'slave',
|
42
|
-
:connection_adapter => 'test',
|
43
|
-
:master => { :username => 'root', :database => 'master' },
|
44
|
-
:slaves => [{ :database => 'slave' }],
|
45
|
-
}
|
46
|
-
end
|
47
|
-
|
48
|
-
let(:database_setup) { default_database_setup }
|
49
|
-
|
50
|
-
let(:mocked_methods) do
|
51
|
-
{
|
52
|
-
:reconnect! => true,
|
53
|
-
:disconnect! => true,
|
54
|
-
:active? => true,
|
55
|
-
}
|
56
|
-
end
|
57
|
-
|
58
|
-
let!(:master_connection) do
|
59
|
-
mock(
|
60
|
-
'master connection',
|
61
|
-
mocked_methods.merge(:open_transactions => 0)
|
62
|
-
).tap do |conn|
|
63
|
-
conn.stub!(:uncached).and_yield
|
64
|
-
ActiveRecord::Base.master_mock = conn
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
let!(:slave_connection) do
|
69
|
-
mock('slave connection', mocked_methods).tap do |conn|
|
70
|
-
conn.stub!(:uncached).and_yield
|
71
|
-
ActiveRecord::Base.slave_mock = conn
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def adapter_connection
|
76
|
-
ActiveRecord::Base.connection
|
77
|
-
end
|
78
|
-
|
79
|
-
SchemaStatements = ActiveRecord::ConnectionAdapters::SchemaStatements.public_instance_methods.map(&:to_sym)
|
80
|
-
SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ] unless defined?(SelectMethods)
|
81
|
-
|
82
|
-
before do
|
83
|
-
ActiveRecord::Base.establish_connection(database_setup)
|
84
|
-
end
|
85
|
-
|
86
|
-
after do
|
87
|
-
ActiveRecord::Base.connection_handler.clear_all_connections!
|
88
|
-
end
|
36
|
+
include_context 'connection setup'
|
37
|
+
let(:connection_adapter) { 'test' }
|
89
38
|
|
90
39
|
describe 'common configuration' do
|
91
40
|
it "should call 'columns' on master" do
|
@@ -132,8 +81,8 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
|
|
132
81
|
end
|
133
82
|
|
134
83
|
it "raises MasterUnavailable if master is not available" do
|
84
|
+
adapter_connection.stub(:connection_error?).and_return(true)
|
135
85
|
master_connection.stub(:open_transactions).and_return(1)
|
136
|
-
master_connection.stub(:connection_error?).and_return(true)
|
137
86
|
master_connection.should_receive(method).with('testing').and_raise(ActiveRecord::StatementInvalid)
|
138
87
|
|
139
88
|
expect do
|
@@ -150,7 +99,7 @@ describe ActiveRecord::ConnectionAdapters::MasterSlaveAdapter do
|
|
150
99
|
end
|
151
100
|
|
152
101
|
it "should raise MasterSlaveAdapter if master is not available" do
|
153
|
-
|
102
|
+
adapter_connection.stub(:connection_error?).and_return(true)
|
154
103
|
master_connection.should_receive(method).and_raise(ActiveRecord::StatementInvalid)
|
155
104
|
|
156
105
|
expect do
|
@@ -0,0 +1,68 @@
|
|
1
|
+
$: << File.expand_path(File.join(File.dirname( __FILE__ ), '..', '..', 'lib'))
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'common/support/connection_setup_helper'
|
5
|
+
require 'common/support/mysql_consistency_examples'
|
6
|
+
require 'active_record/connection_adapters/mysql2_master_slave_adapter'
|
7
|
+
|
8
|
+
module ActiveRecord
|
9
|
+
class Base
|
10
|
+
cattr_accessor :master_mock, :slave_mock
|
11
|
+
|
12
|
+
def self.mysql2_connection(config)
|
13
|
+
config[:database] == 'slave' ? slave_mock : master_mock
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ActiveRecord::ConnectionAdapters::Mysql2MasterSlaveAdapter do
|
19
|
+
include_context 'connection setup'
|
20
|
+
let(:connection_adapter) { 'mysql2' }
|
21
|
+
|
22
|
+
it_should_behave_like 'mysql consistency'
|
23
|
+
|
24
|
+
describe "connection error detection" do
|
25
|
+
{
|
26
|
+
2002 => "query: not connected",
|
27
|
+
2003 => "Can't connect to MySQL server on 'localhost' (3306)",
|
28
|
+
2006 => "MySQL server has gone away",
|
29
|
+
2013 => "Lost connection to MySQL server during query",
|
30
|
+
}.each do |errno, description|
|
31
|
+
it "raises MasterUnavailable for '#{description}' during query execution" do
|
32
|
+
master_connection.stub_chain(:raw_connection, :errno).and_return(errno)
|
33
|
+
master_connection.should_receive(:insert).and_raise(ActiveRecord::StatementInvalid.new("Mysql2::Error: #{description}: INSERT 42"))
|
34
|
+
|
35
|
+
expect do
|
36
|
+
adapter_connection.insert("INSERT 42")
|
37
|
+
end.to raise_error(ActiveRecord::MasterUnavailable)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "doesn't raise anything for '#{description}' during connection" do
|
41
|
+
error = Mysql2::Error.new(description)
|
42
|
+
error.stub(:errno).and_return(errno)
|
43
|
+
ActiveRecord::Base.should_receive(:master_mock).and_raise(error)
|
44
|
+
|
45
|
+
expect do
|
46
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!
|
47
|
+
ActiveRecord::Base.connection
|
48
|
+
end.to_not raise_error
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "raises MasterUnavailable for 'closed MySQL connection' during query execution" do
|
53
|
+
master_connection.should_receive(:insert).and_raise(ActiveRecord::StatementInvalid.new("Mysql2::Error: closed MySQL connection: INSERT 42"))
|
54
|
+
|
55
|
+
expect do
|
56
|
+
adapter_connection.insert("INSERT 42")
|
57
|
+
end.to raise_error(ActiveRecord::MasterUnavailable)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises StatementInvalid for other errors" do
|
61
|
+
master_connection.should_receive(:insert).and_raise(ActiveRecord::StatementInvalid.new("Mysql2::Error: Query execution was interrupted: INSERT 42"))
|
62
|
+
|
63
|
+
expect do
|
64
|
+
adapter_connection.insert("INSERT 42")
|
65
|
+
end.to raise_error(ActiveRecord::StatementInvalid)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
$: << File.expand_path(File.join(File.dirname( __FILE__ ), '..', '..', 'lib'))
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'common/support/connection_setup_helper'
|
5
|
+
require 'common/support/mysql_consistency_examples'
|
6
|
+
require 'active_record/connection_adapters/mysql_master_slave_adapter'
|
7
|
+
|
8
|
+
module ActiveRecord
|
9
|
+
class Base
|
10
|
+
cattr_accessor :master_mock, :slave_mock
|
11
|
+
|
12
|
+
def self.mysql_connection(config)
|
13
|
+
config[:database] == 'slave' ? slave_mock : master_mock
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter do
|
19
|
+
include_context 'connection setup'
|
20
|
+
let(:connection_adapter) { 'mysql' }
|
21
|
+
|
22
|
+
it_should_behave_like 'mysql consistency'
|
23
|
+
|
24
|
+
describe "connection error detection" do
|
25
|
+
{
|
26
|
+
Mysql::Error::CR_CONNECTION_ERROR => "query: not connected",
|
27
|
+
Mysql::Error::CR_CONN_HOST_ERROR => "Can't connect to MySQL server on 'localhost' (3306)",
|
28
|
+
Mysql::Error::CR_SERVER_GONE_ERROR => "MySQL server has gone away",
|
29
|
+
Mysql::Error::CR_SERVER_LOST => "Lost connection to MySQL server during query",
|
30
|
+
}.each do |errno, description|
|
31
|
+
it "raises MasterUnavailable for '#{description}' during query execution" do
|
32
|
+
master_connection.stub_chain(:raw_connection, :errno).and_return(errno)
|
33
|
+
master_connection.should_receive(:insert).and_raise(ActiveRecord::StatementInvalid.new("Mysql::Error: #{description}: INSERT 42"))
|
34
|
+
|
35
|
+
expect do
|
36
|
+
adapter_connection.insert("INSERT 42")
|
37
|
+
end.to raise_error(ActiveRecord::MasterUnavailable)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "doesn't raise anything for '#{description}' during connection" do
|
41
|
+
error = Mysql::Error.new(description)
|
42
|
+
error.stub(:errno).and_return(errno)
|
43
|
+
ActiveRecord::Base.should_receive(:master_mock).and_raise(error)
|
44
|
+
|
45
|
+
expect do
|
46
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!
|
47
|
+
ActiveRecord::Base.connection
|
48
|
+
end.to_not raise_error
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "raises StatementInvalid for other errors" do
|
53
|
+
error = ActiveRecord::StatementInvalid.new("Mysql::Error: Query execution was interrupted: INSERT 42")
|
54
|
+
master_connection.stub_chain(:raw_connection, :errno).and_return(Mysql::Error::ER_QUERY_INTERRUPTED)
|
55
|
+
master_connection.should_receive(:insert).and_raise(error)
|
56
|
+
|
57
|
+
expect do
|
58
|
+
adapter_connection.insert("INSERT 42")
|
59
|
+
end.to raise_error(ActiveRecord::StatementInvalid)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'active_record/connection_adapters/master_slave_adapter'
|
2
|
+
|
3
|
+
SchemaStatements = ActiveRecord::ConnectionAdapters::SchemaStatements.public_instance_methods.map(&:to_sym)
|
4
|
+
SelectMethods = [ :select_all, :select_one, :select_rows, :select_value, :select_values ]
|
5
|
+
|
6
|
+
shared_context 'connection setup' do
|
7
|
+
let(:default_database_setup) do
|
8
|
+
{
|
9
|
+
:adapter => 'master_slave',
|
10
|
+
:username => 'root',
|
11
|
+
:database => 'slave',
|
12
|
+
:connection_adapter => connection_adapter,
|
13
|
+
:master => { :username => 'root', :database => 'master' },
|
14
|
+
:slaves => [{ :database => 'slave' }],
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:database_setup) do
|
19
|
+
default_database_setup
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:mocked_methods) do
|
23
|
+
{
|
24
|
+
:reconnect! => true,
|
25
|
+
:disconnect! => true,
|
26
|
+
:active? => true,
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:master_connection) do
|
31
|
+
stubs = mocked_methods.merge(:open_transactions => 0)
|
32
|
+
mock('master connection', stubs).tap do |conn|
|
33
|
+
conn.stub(:uncached).and_yield
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:slave_connection) do
|
38
|
+
mock('slave connection', mocked_methods).tap do |conn|
|
39
|
+
conn.stub(:uncached).and_yield
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
before do
|
44
|
+
ActiveRecord::Base.master_mock = master_connection
|
45
|
+
ActiveRecord::Base.slave_mock = slave_connection
|
46
|
+
ActiveRecord::Base.establish_connection(database_setup)
|
47
|
+
end
|
48
|
+
|
49
|
+
after do
|
50
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!
|
51
|
+
end
|
52
|
+
|
53
|
+
def adapter_connection
|
54
|
+
ActiveRecord::Base.connection
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'active_record/connection_adapters/master_slave_adapter/clock'
|
2
|
+
|
3
|
+
Clock = ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock
|
4
|
+
|
5
|
+
shared_examples_for 'mysql consistency' do
|
6
|
+
def zero
|
7
|
+
Clock.zero
|
8
|
+
end
|
9
|
+
|
10
|
+
def master_position(pos)
|
11
|
+
Clock.new('', pos)
|
12
|
+
end
|
13
|
+
|
14
|
+
def supports_prepared_statements?
|
15
|
+
described_class == ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter &&
|
16
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.instance_methods.map(&:to_sym).include?(:exec_without_stmt)
|
17
|
+
end
|
18
|
+
|
19
|
+
def select_method
|
20
|
+
supports_prepared_statements? ? :exec_without_stmt : :select_one
|
21
|
+
end
|
22
|
+
|
23
|
+
def should_report_clock(pos, connection, log_file, log_pos, sql)
|
24
|
+
pos = Array(pos)
|
25
|
+
values = pos.map { |p| { log_file => '', log_pos => p } }
|
26
|
+
values.map! { |result| [ result ] } if supports_prepared_statements?
|
27
|
+
|
28
|
+
connection.
|
29
|
+
should_receive(select_method).exactly(pos.length).times.
|
30
|
+
with(sql).
|
31
|
+
and_return(*values)
|
32
|
+
end
|
33
|
+
|
34
|
+
def slave_should_report_clock(pos)
|
35
|
+
should_report_clock(pos, slave_connection, 'Relay_Master_Log_File', 'Exec_Master_Log_Pos', 'SHOW SLAVE STATUS')
|
36
|
+
end
|
37
|
+
|
38
|
+
def master_should_report_clock(pos)
|
39
|
+
should_report_clock(pos, master_connection, 'File', 'Position', 'SHOW MASTER STATUS')
|
40
|
+
end
|
41
|
+
|
42
|
+
SelectMethods.each do |method|
|
43
|
+
it "should send the method '#{method}' to the slave if nil is given" do
|
44
|
+
slave_should_report_clock(0)
|
45
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
46
|
+
new_clock = ActiveRecord::Base.with_consistency(nil) do
|
47
|
+
adapter_connection.send(method, 'testing')
|
48
|
+
end
|
49
|
+
new_clock.should be_a(Clock)
|
50
|
+
new_clock.should equal(zero)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should send the method '#{method}' to the slave if clock.zero is given" do
|
54
|
+
slave_should_report_clock(0)
|
55
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
56
|
+
old_clock = zero
|
57
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
58
|
+
adapter_connection.send(method, 'testing')
|
59
|
+
end
|
60
|
+
new_clock.should be_a(Clock)
|
61
|
+
new_clock.should equal(old_clock)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should send the method '#{method}' to the master if slave hasn't cought up to required clock yet" do
|
65
|
+
slave_should_report_clock(0)
|
66
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
67
|
+
old_clock = master_position(1)
|
68
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
69
|
+
adapter_connection.send(method, 'testing' )
|
70
|
+
end
|
71
|
+
new_clock.should be_a(Clock)
|
72
|
+
new_clock.should equal(old_clock)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should send the method '#{method}' to the master connection if there are open transactions" do
|
76
|
+
master_connection.stub!(:open_transactions).and_return(1)
|
77
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
78
|
+
old_clock = zero
|
79
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
80
|
+
adapter_connection.send(method, 'testing')
|
81
|
+
end
|
82
|
+
new_clock.should be_a(Clock)
|
83
|
+
new_clock.should equal(zero)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should send the method '#{method}' to the master after a write operation" do
|
87
|
+
slave_should_report_clock(0)
|
88
|
+
master_should_report_clock(2)
|
89
|
+
slave_connection.should_receive(method).with('testing').and_return(true)
|
90
|
+
master_connection.should_receive(:update).with('testing').and_return(true)
|
91
|
+
master_connection.should_receive(method).with('testing').and_return(true)
|
92
|
+
old_clock = zero
|
93
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
94
|
+
adapter_connection.send(method, 'testing') # slave
|
95
|
+
adapter_connection.send(:update, 'testing') # master
|
96
|
+
adapter_connection.send(method, 'testing') # master
|
97
|
+
end
|
98
|
+
new_clock.should be_a(Clock)
|
99
|
+
new_clock.should > old_clock
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should update the clock after a transaction" do
|
104
|
+
slave_should_report_clock(0)
|
105
|
+
master_should_report_clock([0, 1, 1])
|
106
|
+
|
107
|
+
slave_connection.
|
108
|
+
should_receive(:select_all).exactly(1).times.with('testing').
|
109
|
+
and_return(true)
|
110
|
+
|
111
|
+
master_connection.
|
112
|
+
should_receive(:update).exactly(3).times.with('testing').
|
113
|
+
and_return(true)
|
114
|
+
master_connection.
|
115
|
+
should_receive(:select_all).exactly(5).times.with('testing').
|
116
|
+
and_return(true)
|
117
|
+
%w(begin_db_transaction
|
118
|
+
commit_db_transaction
|
119
|
+
increment_open_transactions
|
120
|
+
decrement_open_transactions
|
121
|
+
outside_transaction?).each do |txstmt|
|
122
|
+
master_connection.should_receive(txstmt).exactly(1).times
|
123
|
+
end
|
124
|
+
|
125
|
+
master_connection.
|
126
|
+
should_receive('open_transactions').exactly(13).times.
|
127
|
+
and_return(
|
128
|
+
# adapter: with_consistency, select_all, update, select_all
|
129
|
+
0, 0, 0, 0,
|
130
|
+
# connection: transaction
|
131
|
+
0,
|
132
|
+
# adapter: select_all, update, select_all, commit_db_transaction
|
133
|
+
1, 1, 1, 0,
|
134
|
+
# connection: transaction (ensure)
|
135
|
+
0,
|
136
|
+
# adapter: select_all, update, select_all
|
137
|
+
0, 0, 0
|
138
|
+
)
|
139
|
+
|
140
|
+
old_clock = zero
|
141
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
142
|
+
adapter_connection.send(:select_all, 'testing') # slave s=0 m=0
|
143
|
+
adapter_connection.send(:update, 'testing') # master s=0 m=1
|
144
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=1
|
145
|
+
|
146
|
+
ActiveRecord::Base.transaction do
|
147
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=1
|
148
|
+
adapter_connection.send(:update, 'testing') # master s=0 m=1
|
149
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=1
|
150
|
+
end
|
151
|
+
|
152
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=2
|
153
|
+
adapter_connection.send(:update, 'testing') # master s=0 m=3
|
154
|
+
adapter_connection.send(:select_all, 'testing') # master s=0 m=3
|
155
|
+
end
|
156
|
+
|
157
|
+
new_clock.should > old_clock
|
158
|
+
end
|
159
|
+
|
160
|
+
context "with nested with_consistency" do
|
161
|
+
it "should return the same clock if not writing and no lag" do
|
162
|
+
slave_should_report_clock(0)
|
163
|
+
slave_connection.
|
164
|
+
should_receive(:select_one).exactly(3).times.with('testing').
|
165
|
+
and_return(true)
|
166
|
+
|
167
|
+
old_clock = zero
|
168
|
+
new_clock = ActiveRecord::Base.with_consistency(old_clock) do
|
169
|
+
adapter_connection.send(:select_one, 'testing')
|
170
|
+
ActiveRecord::Base.with_consistency(old_clock) do
|
171
|
+
adapter_connection.send(:select_one, 'testing')
|
172
|
+
end
|
173
|
+
adapter_connection.send(:select_one, 'testing')
|
174
|
+
end
|
175
|
+
new_clock.should equal(old_clock)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "requesting a newer clock should return a new clock" do
|
179
|
+
adapter_connection.
|
180
|
+
should_receive('slave_consistent?').exactly(2).times.
|
181
|
+
and_return(true, false)
|
182
|
+
slave_connection.
|
183
|
+
should_receive(:select_all).exactly(2).times.with('testing').
|
184
|
+
and_return(true)
|
185
|
+
master_connection.
|
186
|
+
should_receive(:select_all).exactly(1).times.with('testing').
|
187
|
+
and_return(true)
|
188
|
+
|
189
|
+
start_clock = zero
|
190
|
+
inner_clock = zero
|
191
|
+
outer_clock = ActiveRecord::Base.with_consistency(start_clock) do
|
192
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
193
|
+
inner_clock = ActiveRecord::Base.with_consistency(master_position(1)) do
|
194
|
+
adapter_connection.send(:select_all, 'testing') # master
|
195
|
+
end
|
196
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
197
|
+
end
|
198
|
+
|
199
|
+
start_clock.should equal(outer_clock)
|
200
|
+
inner_clock.should > start_clock
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should do the right thing when nested inside with_master" do
|
205
|
+
slave_should_report_clock(0)
|
206
|
+
slave_connection.should_receive(:select_all).exactly(1).times.with('testing').and_return(true)
|
207
|
+
master_connection.should_receive(:select_all).exactly(2).times.with('testing').and_return(true)
|
208
|
+
ActiveRecord::Base.with_master do
|
209
|
+
adapter_connection.send(:select_all, 'testing') # master
|
210
|
+
ActiveRecord::Base.with_consistency(zero) do
|
211
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
212
|
+
end
|
213
|
+
adapter_connection.send(:select_all, 'testing') # master
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should do the right thing when nested inside with_slave" do
|
218
|
+
slave_should_report_clock(0)
|
219
|
+
slave_connection.should_receive(:select_all).exactly(3).times.with('testing').and_return(true)
|
220
|
+
ActiveRecord::Base.with_slave do
|
221
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
222
|
+
ActiveRecord::Base.with_consistency(zero) do
|
223
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
224
|
+
end
|
225
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should do the right thing when wrapping with_master" do
|
230
|
+
slave_should_report_clock(0)
|
231
|
+
slave_connection.should_receive(:select_all).exactly(2).times.with('testing').and_return(true)
|
232
|
+
master_connection.should_receive(:select_all).exactly(1).times.with('testing').and_return(true)
|
233
|
+
ActiveRecord::Base.with_consistency(zero) do
|
234
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
235
|
+
ActiveRecord::Base.with_master do
|
236
|
+
adapter_connection.send(:select_all, 'testing') # master
|
237
|
+
end
|
238
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
it "should do the right thing when wrapping with_slave" do
|
243
|
+
slave_should_report_clock(0)
|
244
|
+
slave_connection.should_receive(:select_all).exactly(1).times.with('testing').and_return(true)
|
245
|
+
master_connection.should_receive(:select_all).exactly(2).times.with('testing').and_return(true)
|
246
|
+
ActiveRecord::Base.with_consistency(master_position(1)) do
|
247
|
+
adapter_connection.send(:select_all, 'testing') # master
|
248
|
+
ActiveRecord::Base.with_slave do
|
249
|
+
adapter_connection.send(:select_all, 'testing') # slave
|
250
|
+
end
|
251
|
+
adapter_connection.send(:select_all, 'testing') # master
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should accept clock as string" do
|
256
|
+
slave_should_report_clock(0)
|
257
|
+
slave_connection.should_receive(:select_all).with('testing')
|
258
|
+
|
259
|
+
ActiveRecord::Base.with_consistency("@0") do
|
260
|
+
adapter_connection.send(:select_all, 'testing')
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -2,7 +2,7 @@ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
|
|
2
2
|
|
3
3
|
require 'rspec'
|
4
4
|
require 'master_slave_adapter'
|
5
|
-
require 'integration/
|
5
|
+
require 'integration/support/shared_mysql_examples'
|
6
6
|
|
7
7
|
describe "ActiveRecord::ConnectionAdapters::Mysql2MasterSlaveAdapter" do
|
8
8
|
let(:connection_adapter) { 'mysql2' }
|
@@ -2,7 +2,7 @@ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
|
|
2
2
|
|
3
3
|
require 'rspec'
|
4
4
|
require 'master_slave_adapter'
|
5
|
-
require 'integration/
|
5
|
+
require 'integration/support/shared_mysql_examples'
|
6
6
|
|
7
7
|
describe "ActiveRecord::ConnectionAdapters::MysqlMasterSlaveAdapter" do
|
8
8
|
let(:connection_adapter) { 'mysql' }
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require 'integration/
|
1
|
+
require 'integration/support/mysql_setup_helper'
|
2
2
|
|
3
3
|
shared_examples_for "a MySQL MasterSlaveAdapter" do
|
4
|
-
include
|
4
|
+
include MysqlSetupHelper
|
5
5
|
|
6
6
|
let(:configuration) do
|
7
7
|
{
|
@@ -20,7 +20,7 @@ shared_examples_for "a MySQL MasterSlaveAdapter" do
|
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
23
|
-
let(:test_table) {
|
23
|
+
let(:test_table) { MysqlSetupHelper::TEST_TABLE }
|
24
24
|
|
25
25
|
def connection
|
26
26
|
ActiveRecord::Base.connection
|