makara 0.3.5
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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +28 -0
- data/CHANGELOG.md +27 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +278 -0
- data/Rakefile +9 -0
- data/gemfiles/ar30.gemfile +30 -0
- data/gemfiles/ar31.gemfile +29 -0
- data/gemfiles/ar32.gemfile +29 -0
- data/gemfiles/ar40.gemfile +15 -0
- data/gemfiles/ar41.gemfile +15 -0
- data/gemfiles/ar42.gemfile +15 -0
- data/lib/active_record/connection_adapters/jdbcmysql_makara_adapter.rb +25 -0
- data/lib/active_record/connection_adapters/jdbcpostgresql_makara_adapter.rb +25 -0
- data/lib/active_record/connection_adapters/makara_abstract_adapter.rb +209 -0
- data/lib/active_record/connection_adapters/makara_jdbcmysql_adapter.rb +25 -0
- data/lib/active_record/connection_adapters/makara_jdbcpostgresql_adapter.rb +25 -0
- data/lib/active_record/connection_adapters/makara_mysql2_adapter.rb +44 -0
- data/lib/active_record/connection_adapters/makara_postgresql_adapter.rb +44 -0
- data/lib/active_record/connection_adapters/mysql2_makara_adapter.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql_makara_adapter.rb +44 -0
- data/lib/makara.rb +25 -0
- data/lib/makara/cache.rb +53 -0
- data/lib/makara/cache/memory_store.rb +28 -0
- data/lib/makara/cache/noop_store.rb +15 -0
- data/lib/makara/config_parser.rb +200 -0
- data/lib/makara/connection_wrapper.rb +170 -0
- data/lib/makara/context.rb +46 -0
- data/lib/makara/error_handler.rb +39 -0
- data/lib/makara/errors/all_connections_blacklisted.rb +13 -0
- data/lib/makara/errors/blacklist_connection.rb +14 -0
- data/lib/makara/errors/no_connections_available.rb +14 -0
- data/lib/makara/logging/logger.rb +23 -0
- data/lib/makara/logging/subscriber.rb +38 -0
- data/lib/makara/middleware.rb +109 -0
- data/lib/makara/pool.rb +188 -0
- data/lib/makara/proxy.rb +277 -0
- data/lib/makara/railtie.rb +14 -0
- data/lib/makara/version.rb +15 -0
- data/makara.gemspec +19 -0
- data/spec/active_record/connection_adapters/makara_abstract_adapter_error_handling_spec.rb +92 -0
- data/spec/active_record/connection_adapters/makara_abstract_adapter_spec.rb +114 -0
- data/spec/active_record/connection_adapters/makara_mysql2_adapter_spec.rb +183 -0
- data/spec/active_record/connection_adapters/makara_postgresql_adapter_spec.rb +121 -0
- data/spec/cache_spec.rb +59 -0
- data/spec/config_parser_spec.rb +102 -0
- data/spec/connection_wrapper_spec.rb +33 -0
- data/spec/context_spec.rb +107 -0
- data/spec/middleware_spec.rb +84 -0
- data/spec/pool_spec.rb +158 -0
- data/spec/proxy_spec.rb +182 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/support/configurator.rb +13 -0
- data/spec/support/deep_dup.rb +12 -0
- data/spec/support/mock_objects.rb +67 -0
- data/spec/support/mysql2_database.yml +17 -0
- data/spec/support/mysql2_database_with_custom_errors.yml +17 -0
- data/spec/support/pool_extensions.rb +14 -0
- data/spec/support/postgresql_database.yml +13 -0
- data/spec/support/proxy_extensions.rb +33 -0
- data/spec/support/schema.rb +7 -0
- metadata +144 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
module Makara
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
|
4
|
+
config.app_middleware.use 'Makara::Middleware'
|
5
|
+
|
6
|
+
|
7
|
+
initializer "makara.initialize_logger" do |app|
|
8
|
+
ActiveRecord::LogSubscriber.log_subscribers.each do |subscriber|
|
9
|
+
subscriber.extend ::Makara::Logging::Subscriber
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
data/makara.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/makara/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Mike Nelson"]
|
6
|
+
gem.email = ["mike@mikeonrails.com"]
|
7
|
+
gem.description = %q{Read-write split your DB yo}
|
8
|
+
gem.summary = %q{Read-write split your DB yo}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "makara"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Makara::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'activerecord', '>= 3.0.0'
|
19
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record/connection_adapters/makara_abstract_adapter'
|
3
|
+
|
4
|
+
describe ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter::ErrorHandler do
|
5
|
+
|
6
|
+
let(:handler){ described_class.new }
|
7
|
+
let(:proxy) { FakeAdapter.new(config(1,1)) }
|
8
|
+
let(:connection){ proxy.master_pool.connections.first }
|
9
|
+
|
10
|
+
[
|
11
|
+
%|Mysql::Error: : INSERT INTO `watchers` (`user_id`, `watchable_id`, `watchable_type`) VALUES|,
|
12
|
+
%|PGError: ERROR: column items.user_id does not exist LINE 1: SELECT "items".* FROM "items" WHERE ("items".user_id = 4) OR|
|
13
|
+
].each do |msg|
|
14
|
+
it "should properly evaluate errors like: #{msg}" do
|
15
|
+
expect(handler).not_to be_connection_message(msg)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should raise the error' do
|
19
|
+
expect{
|
20
|
+
handler.handle(connection) do
|
21
|
+
raise msg
|
22
|
+
end
|
23
|
+
}.to raise_error(msg)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
[
|
28
|
+
%|Mysql2::Error: closed MySQL connection: SELECT `users`.* FROM `users`|,
|
29
|
+
%|Mysql2::Error: MySQL server has gone away: SELECT `users`.* FROM `users`|,
|
30
|
+
%|Mysql2::Error (Can't connect to MySQL server on '123.456.789.234' (111))|,
|
31
|
+
%|Mysql2::Error (Cannot connect to MySQL server on '123.456.789.234' (111))|,
|
32
|
+
%|Mysql2::Error Can't connect to MySQL server on '123.456.789.235' (111)|,
|
33
|
+
%|Mysql2::Error: Timeout waiting for a response from the last query|,
|
34
|
+
%|Can't connect to MySQL server on '123.456.789.235' (111)|,
|
35
|
+
%|Mysql2::Error: Lost connection to MySQL server during query: SELECT `geographies`.* FROM `geographies`|,
|
36
|
+
%|PGError: server closed the connection unexpectedly This probably me|,
|
37
|
+
%|Could not connect to server: Connection refused Is the server running on host|,
|
38
|
+
%|PG::AdminShutdown: FATAL: terminating connection due to administrator command FATAL: terminating connection due to administrator command|,
|
39
|
+
%|PG::ConnectionBad: PQconsumeInput() SSL connection has been closed unexpectedly: SELECT 1 AS one FROM "users" WHERE "users"."registration_ip" = '10.0.2.2' LIMIT 1|,
|
40
|
+
%|PG::UnableToSend: no connection to the server|,
|
41
|
+
%|PG::ConnectionBad (could not connect to server: Connection refused|,
|
42
|
+
%|PG::ConnectionBad: PQsocket() can't get socket descriptor:|,
|
43
|
+
%|org.postgresql.util.PSQLException: Connection to localhost:123 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.|,
|
44
|
+
%|PG::ConnectionBad: timeout expired|,
|
45
|
+
%|PG::ConnectionBad: could not translate host name "some.sample.com" to address: Name or service not known|,
|
46
|
+
%|PG::ConnectionBad: FATAL: the database system is starting up|,
|
47
|
+
%|PG::ConnectionBad: FATAL: the database system is shutting down|
|
48
|
+
].each do |msg|
|
49
|
+
it "should properly evaluate connection messages like: #{msg}" do
|
50
|
+
expect(handler).to be_connection_message(msg)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should blacklist the connection' do
|
54
|
+
expect {
|
55
|
+
handler.handle(connection) do
|
56
|
+
raise msg
|
57
|
+
end
|
58
|
+
}.to raise_error(Makara::Errors::BlacklistConnection)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'custom errors' do
|
63
|
+
|
64
|
+
let(:config_path) { File.join(File.expand_path('../../../', __FILE__), 'support', 'mysql2_database_with_custom_errors.yml') }
|
65
|
+
let(:config) { YAML.load_file(config_path)['test'] }
|
66
|
+
let(:handler){ described_class.new }
|
67
|
+
let(:proxy) { FakeAdapter.new(config) }
|
68
|
+
let(:connection){ proxy.master_pool.connections.first }
|
69
|
+
let(:msg1) { "ActiveRecord::StatementInvalid: Mysql2::Error: Unknown command1: SELECT `users`.* FROM `users` WHERE `users`.`id` = 53469 LIMIT 1" }
|
70
|
+
let(:msg2) { "activeRecord::statementInvalid: mysql2::error: unknown command2: SELECT `users`.* FROM `users` WHERE `users`.`id` = 53469 LIMIT 1" }
|
71
|
+
let(:msg3) { "ActiveRecord::StatementInvalid: Mysql2::Error: Unknown command3: SELECT `users`.* FROM `users` WHERE `users`.`id` = 53469 LIMIT 1" }
|
72
|
+
let(:msg4) { "ActiveRecord::StatementInvalid: Mysql2::Error: Unknown command3:" }
|
73
|
+
|
74
|
+
it "identifies custom errors" do
|
75
|
+
expect(handler).to be_custom_error_message(connection, msg1)
|
76
|
+
expect(handler).to be_custom_error_message(connection, msg2)
|
77
|
+
expect(handler).to_not be_custom_error_message(connection, msg3)
|
78
|
+
expect(handler).to be_custom_error_message(connection, msg4)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "blacklists the connection" do
|
82
|
+
expect {
|
83
|
+
handler.handle(connection) do
|
84
|
+
raise msg1
|
85
|
+
end
|
86
|
+
}.to raise_error(Makara::Errors::BlacklistConnection)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record/connection_adapters/makara_abstract_adapter'
|
3
|
+
|
4
|
+
describe ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter do
|
5
|
+
|
6
|
+
let(:klass){ FakeAdapter }
|
7
|
+
|
8
|
+
{
|
9
|
+
'insert into dogs...' => true,
|
10
|
+
'insert into cats (select * from felines)' => true,
|
11
|
+
'savepoint active_record_1' => true,
|
12
|
+
'begin' => true,
|
13
|
+
'rollback' => true,
|
14
|
+
'update users set' => true,
|
15
|
+
'delete from people' => true,
|
16
|
+
'release savepoint' => true,
|
17
|
+
'show tables' => true,
|
18
|
+
'show fields' => true,
|
19
|
+
'describe table' => true,
|
20
|
+
'show index' => true,
|
21
|
+
'set @@things' => true,
|
22
|
+
'SET @@things' => true,
|
23
|
+
'commit' => true,
|
24
|
+
'select * from felines' => false,
|
25
|
+
' select * from felines' => false,
|
26
|
+
'select * from users for update' => true,
|
27
|
+
' select * from users for update' => true,
|
28
|
+
'select * from users lock in share mode' => true,
|
29
|
+
'select * from users where name = "for update"' => false,
|
30
|
+
'select * from users where name = "lock in share mode"' => false
|
31
|
+
}.each do |sql, should_go_to_master|
|
32
|
+
|
33
|
+
it "determines that \"#{sql}\" #{should_go_to_master ? 'requires' : 'does not require'} master" do
|
34
|
+
proxy = klass.new(config(1,1))
|
35
|
+
expect(proxy.master_for?(sql)).to eq(should_go_to_master)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
{
|
42
|
+
"SET @@things" => true,
|
43
|
+
"INSERT INTO wisdom ('The truth will set you free.')" => false,
|
44
|
+
"INSERT INTO wisdom ('The truth will\nset you free.')" => false,
|
45
|
+
"UPDATE dogs SET max_treats = 10 WHERE max_treats IS NULL" => false,
|
46
|
+
%Q{
|
47
|
+
UPDATE
|
48
|
+
dogs
|
49
|
+
SET
|
50
|
+
max_treats = 10
|
51
|
+
WHERE
|
52
|
+
max_treats IS NULL
|
53
|
+
} => false
|
54
|
+
}.each do |sql, should_send_to_all_connections|
|
55
|
+
|
56
|
+
it "determines that \"#{sql}\" #{should_send_to_all_connections ? 'should' : 'should not'} be sent to all underlying connections" do
|
57
|
+
proxy = klass.new(config(1,1))
|
58
|
+
proxy.master_pool.connections.each{|con| expect(con).to receive(:execute).with(sql).once}
|
59
|
+
proxy.slave_pool.connections.each do |con|
|
60
|
+
if should_send_to_all_connections
|
61
|
+
expect(con).to receive(:execute).with(sql).once
|
62
|
+
else
|
63
|
+
expect(con).to receive(:execute).with(sql).never
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
proxy.execute(sql)
|
68
|
+
|
69
|
+
if should_send_to_all_connections
|
70
|
+
expect(proxy.master_context).to be_nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
{
|
77
|
+
'show full tables' => false,
|
78
|
+
'show full table' => false,
|
79
|
+
'show index' => false,
|
80
|
+
'show indexes' => false,
|
81
|
+
'describe stuff' => false,
|
82
|
+
'explain things' => false,
|
83
|
+
'show database' => false,
|
84
|
+
'show schema' => false,
|
85
|
+
'show view' => false,
|
86
|
+
'show views' => false,
|
87
|
+
'show table' => false,
|
88
|
+
'show tables' => false,
|
89
|
+
'set @@things' => false,
|
90
|
+
'update users' => true,
|
91
|
+
'insert into' => true,
|
92
|
+
'delete from' => true,
|
93
|
+
'begin transaction' => true,
|
94
|
+
'begin deferred transaction' => true,
|
95
|
+
'commit transaction' => true,
|
96
|
+
'rollback transaction' => true,
|
97
|
+
%Q{
|
98
|
+
UPDATE
|
99
|
+
dogs
|
100
|
+
SET
|
101
|
+
max_treats = 10
|
102
|
+
WHERE
|
103
|
+
max_treats IS NULL
|
104
|
+
} => true
|
105
|
+
}.each do |sql,should_stick|
|
106
|
+
|
107
|
+
it "should #{should_stick ? 'stick' : 'not stick'} to master if handling sql like \"#{sql}\"" do
|
108
|
+
proxy = klass.new(config(0,0))
|
109
|
+
expect(proxy.would_stick?(sql)).to eq(should_stick)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
3
|
+
|
4
|
+
describe 'MakaraMysql2Adapter' do
|
5
|
+
|
6
|
+
let(:db_username){ ENV['TRAVIS'] ? 'travis' : 'root' }
|
7
|
+
|
8
|
+
let(:config){
|
9
|
+
base = YAML.load_file(File.expand_path('spec/support/mysql2_database.yml'))['test']
|
10
|
+
base
|
11
|
+
}
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
ActiveRecord::Base.clear_all_connections!
|
15
|
+
change_context
|
16
|
+
end
|
17
|
+
|
18
|
+
context "unconnected" do
|
19
|
+
|
20
|
+
it 'should allow a connection to be established' do
|
21
|
+
ActiveRecord::Base.establish_connection(config)
|
22
|
+
expect(ActiveRecord::Base.connection).to be_instance_of(ActiveRecord::ConnectionAdapters::MakaraMysql2Adapter)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should not blow up if a connection fails' do
|
26
|
+
wrong_config = config.deep_dup
|
27
|
+
wrong_config['makara']['connections'].select{|h| h['role'] == 'slave' }.each{|h| h['username'] = 'other'}
|
28
|
+
|
29
|
+
original_method = ActiveRecord::Base.method(:mysql2_connection)
|
30
|
+
|
31
|
+
allow(ActiveRecord::Base).to receive(:mysql2_connection) do |config|
|
32
|
+
if config[:username] == 'other'
|
33
|
+
raise "could not connect"
|
34
|
+
else
|
35
|
+
original_method.call(config)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
ActiveRecord::Base.establish_connection(wrong_config)
|
40
|
+
ActiveRecord::Base.connection
|
41
|
+
|
42
|
+
load(File.dirname(__FILE__) + '/../../support/schema.rb')
|
43
|
+
Makara::Context.set_current Makara::Context.generate
|
44
|
+
|
45
|
+
allow(ActiveRecord::Base).to receive(:mysql2_connection) do |config|
|
46
|
+
config[:username] = db_username
|
47
|
+
original_method.call(config)
|
48
|
+
end
|
49
|
+
|
50
|
+
ActiveRecord::Base.connection.slave_pool.connections.each(&:_makara_whitelist!)
|
51
|
+
ActiveRecord::Base.connection.slave_pool.provide do |con|
|
52
|
+
res = con.execute('SELECT count(*) FROM users')
|
53
|
+
if defined?(JRUBY_VERSION)
|
54
|
+
expect(res[0]).to eq('count(*)' => 0)
|
55
|
+
else
|
56
|
+
expect(res.to_a[0][0]).to eq(0)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
ActiveRecord::Base.remove_connection
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should execute a send_to_all against master even if no slaves are connected' do
|
64
|
+
ActiveRecord::Base.establish_connection(config)
|
65
|
+
connection = ActiveRecord::Base.connection
|
66
|
+
|
67
|
+
connection.slave_pool.connections.each do |c|
|
68
|
+
allow(c).to receive(:_makara_blacklisted?){ true }
|
69
|
+
allow(c).to receive(:_makara_connected?){ false }
|
70
|
+
expect(c).to receive(:execute).with('SET @t1 = 1').never
|
71
|
+
end
|
72
|
+
|
73
|
+
connection.master_pool.connections.each do |c|
|
74
|
+
expect(c).to receive(:execute).with('SET @t1 = 1')
|
75
|
+
end
|
76
|
+
|
77
|
+
expect{
|
78
|
+
connection.execute('SET @t1 = 1')
|
79
|
+
}.not_to raise_error
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should execute a send_to_all and raise a NoConnectionsAvailable error' do
|
83
|
+
ActiveRecord::Base.establish_connection(config)
|
84
|
+
connection = ActiveRecord::Base.connection
|
85
|
+
|
86
|
+
(connection.slave_pool.connections | connection.master_pool.connections).each do |c|
|
87
|
+
allow(c).to receive(:_makara_blacklisted?){ true }
|
88
|
+
allow(c).to receive(:_makara_connected?){ false }
|
89
|
+
expect(c).to receive(:execute).with('SET @t1 = 1').never
|
90
|
+
end
|
91
|
+
|
92
|
+
expect{
|
93
|
+
connection.execute('SET @t1 = 1')
|
94
|
+
}.to raise_error(Makara::Errors::NoConnectionsAvailable)
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'with the connection established and schema loaded' do
|
101
|
+
|
102
|
+
let(:connection) { ActiveRecord::Base.connection }
|
103
|
+
|
104
|
+
before do
|
105
|
+
ActiveRecord::Base.establish_connection(config)
|
106
|
+
load(File.dirname(__FILE__) + '/../../support/schema.rb')
|
107
|
+
change_context
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
it 'should have one master and two slaves' do
|
112
|
+
expect(connection.master_pool.connection_count).to eq(1)
|
113
|
+
expect(connection.slave_pool.connection_count).to eq(2)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should allow real queries to work' do
|
117
|
+
connection.execute("INSERT INTO users (name) VALUES ('John')")
|
118
|
+
|
119
|
+
connection.master_pool.connections.each do |master|
|
120
|
+
expect(master).to receive(:execute).never
|
121
|
+
end
|
122
|
+
|
123
|
+
change_context
|
124
|
+
|
125
|
+
res = connection.execute('SELECT name FROM users ORDER BY id DESC LIMIT 1')
|
126
|
+
|
127
|
+
if defined?(JRUBY_VERSION)
|
128
|
+
expect(res[0]['name']).to eq('John')
|
129
|
+
else
|
130
|
+
expect(res.to_a[0][0]).to eq('John')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should send SET operations to each connection' do
|
135
|
+
connection.master_pool.connections.each do |con|
|
136
|
+
expect(con).to receive(:execute).with('SET @t1 = 1').once
|
137
|
+
end
|
138
|
+
|
139
|
+
connection.slave_pool.connections.each do |con|
|
140
|
+
expect(con).to receive(:execute).with('SET @t1 = 1').once
|
141
|
+
end
|
142
|
+
connection.execute("SET @t1 = 1")
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'should send reads to the slave' do
|
146
|
+
# ensure the next connection will be the first one
|
147
|
+
connection.slave_pool.instance_variable_set('@current_idx', connection.slave_pool.connections.length)
|
148
|
+
|
149
|
+
con = connection.slave_pool.connections.first
|
150
|
+
expect(con).to receive(:execute).with('SELECT * FROM users').once
|
151
|
+
|
152
|
+
connection.execute('SELECT * FROM users')
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should send writes to master' do
|
156
|
+
con = connection.master_pool.connections.first
|
157
|
+
expect(con).to receive(:execute).with('UPDATE users SET name = "bob" WHERE id = 1')
|
158
|
+
connection.execute('UPDATE users SET name = "bob" WHERE id = 1')
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should allow reconnecting' do
|
162
|
+
connection.object_id
|
163
|
+
connection.reconnect!
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should allow reconnecting when one of the nodes is blacklisted' do
|
167
|
+
con = connection.slave_pool.connections.first
|
168
|
+
allow(con).to receive(:_makara_blacklisted?){ true }
|
169
|
+
connection.reconnect!
|
170
|
+
end
|
171
|
+
|
172
|
+
if !defined?(JRUBY_VERSION)
|
173
|
+
# yml settings only for mysql2
|
174
|
+
it 'should blacklist on timeout' do
|
175
|
+
expect {
|
176
|
+
connection.execute('SELECT SLEEP(2)') # read timeout set to 1
|
177
|
+
}.to raise_error(Makara::Errors::AllConnectionsBlacklisted)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
3
|
+
|
4
|
+
describe 'MakaraPostgreSQLAdapter' do
|
5
|
+
|
6
|
+
let(:db_username){ ENV['TRAVIS'] ? 'postgres' : `whoami`.chomp }
|
7
|
+
|
8
|
+
let(:config){
|
9
|
+
base = YAML.load_file(File.expand_path('spec/support/postgresql_database.yml'))['test']
|
10
|
+
base['username'] = db_username
|
11
|
+
base
|
12
|
+
}
|
13
|
+
|
14
|
+
let(:connection) { ActiveRecord::Base.connection }
|
15
|
+
|
16
|
+
before :each do
|
17
|
+
ActiveRecord::Base.clear_all_connections!
|
18
|
+
change_context
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
it 'should allow a connection to be established' do
|
23
|
+
ActiveRecord::Base.establish_connection(config)
|
24
|
+
expect(ActiveRecord::Base.connection).to be_instance_of(ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with the connection established and schema loaded' do
|
28
|
+
|
29
|
+
before do
|
30
|
+
ActiveRecord::Base.establish_connection(config)
|
31
|
+
load(File.dirname(__FILE__) + '/../../support/schema.rb')
|
32
|
+
change_context
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
it 'should have one master and two slaves' do
|
37
|
+
expect(connection.master_pool.connection_count).to eq(1)
|
38
|
+
expect(connection.slave_pool.connection_count).to eq(2)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should allow real queries to work' do
|
42
|
+
connection.execute('INSERT INTO users (name) VALUES (\'John\')')
|
43
|
+
|
44
|
+
connection.master_pool.connections.each do |master|
|
45
|
+
expect(master).to receive(:execute).never
|
46
|
+
end
|
47
|
+
|
48
|
+
change_context
|
49
|
+
res = connection.execute('SELECT name FROM users ORDER BY id DESC LIMIT 1')
|
50
|
+
|
51
|
+
expect(res.to_a[0]['name']).to eq('John')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should send SET operations to each connection' do
|
55
|
+
connection.master_pool.connections.each do |con|
|
56
|
+
expect(con).to receive(:execute).with("SET TimeZone = 'UTC'").once
|
57
|
+
end
|
58
|
+
|
59
|
+
connection.slave_pool.connections.each do |con|
|
60
|
+
expect(con).to receive(:execute).with("SET TimeZone = 'UTC'").once
|
61
|
+
end
|
62
|
+
connection.execute("SET TimeZone = 'UTC'")
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should send reads to the slave' do
|
66
|
+
# ensure the next connection will be the first one
|
67
|
+
connection.slave_pool.instance_variable_set('@current_idx', connection.slave_pool.connections.length)
|
68
|
+
|
69
|
+
con = connection.slave_pool.connections.first
|
70
|
+
expect(con).to receive(:execute).with('SELECT * FROM users').once
|
71
|
+
|
72
|
+
connection.execute('SELECT * FROM users')
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should send writes to master' do
|
76
|
+
con = connection.master_pool.connections.first
|
77
|
+
expect(con).to receive(:execute).with('UPDATE users SET name = "bob" WHERE id = 1')
|
78
|
+
connection.execute('UPDATE users SET name = "bob" WHERE id = 1')
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'without live connections' do
|
84
|
+
it 'should raise errors on read or write' do
|
85
|
+
allow(ActiveRecord::Base).to receive(:postgresql_connection).and_raise(StandardError.new('could not connect to server: Connection refused'))
|
86
|
+
|
87
|
+
ActiveRecord::Base.establish_connection(config)
|
88
|
+
expect { connection.execute('SELECT * FROM users') }.to raise_error(Makara::Errors::NoConnectionsAvailable)
|
89
|
+
expect { connection.execute('INSERT INTO users (name) VALUES (\'John\')') }.to raise_error(Makara::Errors::NoConnectionsAvailable)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'with only master connection' do
|
94
|
+
it 'should not raise errors on read and write' do
|
95
|
+
custom_config = config.deep_dup
|
96
|
+
custom_config['makara']['connections'].select{|h| h['role'] == 'slave' }.each{|h| h['port'] = '1'}
|
97
|
+
|
98
|
+
ActiveRecord::Base.establish_connection(custom_config)
|
99
|
+
load(File.dirname(__FILE__) + '/../../support/schema.rb')
|
100
|
+
|
101
|
+
connection.execute('SELECT * FROM users')
|
102
|
+
connection.execute('INSERT INTO users (name) VALUES (\'John\')')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'with only slave connection' do
|
107
|
+
it 'should raise error only on write' do
|
108
|
+
ActiveRecord::Base.establish_connection(config)
|
109
|
+
load(File.dirname(__FILE__) + '/../../support/schema.rb')
|
110
|
+
ActiveRecord::Base.clear_all_connections!
|
111
|
+
|
112
|
+
custom_config = config.deep_dup
|
113
|
+
custom_config['makara']['connections'].select{|h| h['role'] == 'master' }.each{|h| h['port'] = '1'}
|
114
|
+
|
115
|
+
ActiveRecord::Base.establish_connection(custom_config)
|
116
|
+
|
117
|
+
connection.execute('SELECT * FROM users')
|
118
|
+
expect { connection.execute('INSERT INTO users (name) VALUES (\'John\')') }.to raise_error(Makara::Errors::NoConnectionsAvailable)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|