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.
- 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
|