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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +28 -0
  7. data/CHANGELOG.md +27 -0
  8. data/Gemfile +18 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +278 -0
  11. data/Rakefile +9 -0
  12. data/gemfiles/ar30.gemfile +30 -0
  13. data/gemfiles/ar31.gemfile +29 -0
  14. data/gemfiles/ar32.gemfile +29 -0
  15. data/gemfiles/ar40.gemfile +15 -0
  16. data/gemfiles/ar41.gemfile +15 -0
  17. data/gemfiles/ar42.gemfile +15 -0
  18. data/lib/active_record/connection_adapters/jdbcmysql_makara_adapter.rb +25 -0
  19. data/lib/active_record/connection_adapters/jdbcpostgresql_makara_adapter.rb +25 -0
  20. data/lib/active_record/connection_adapters/makara_abstract_adapter.rb +209 -0
  21. data/lib/active_record/connection_adapters/makara_jdbcmysql_adapter.rb +25 -0
  22. data/lib/active_record/connection_adapters/makara_jdbcpostgresql_adapter.rb +25 -0
  23. data/lib/active_record/connection_adapters/makara_mysql2_adapter.rb +44 -0
  24. data/lib/active_record/connection_adapters/makara_postgresql_adapter.rb +44 -0
  25. data/lib/active_record/connection_adapters/mysql2_makara_adapter.rb +44 -0
  26. data/lib/active_record/connection_adapters/postgresql_makara_adapter.rb +44 -0
  27. data/lib/makara.rb +25 -0
  28. data/lib/makara/cache.rb +53 -0
  29. data/lib/makara/cache/memory_store.rb +28 -0
  30. data/lib/makara/cache/noop_store.rb +15 -0
  31. data/lib/makara/config_parser.rb +200 -0
  32. data/lib/makara/connection_wrapper.rb +170 -0
  33. data/lib/makara/context.rb +46 -0
  34. data/lib/makara/error_handler.rb +39 -0
  35. data/lib/makara/errors/all_connections_blacklisted.rb +13 -0
  36. data/lib/makara/errors/blacklist_connection.rb +14 -0
  37. data/lib/makara/errors/no_connections_available.rb +14 -0
  38. data/lib/makara/logging/logger.rb +23 -0
  39. data/lib/makara/logging/subscriber.rb +38 -0
  40. data/lib/makara/middleware.rb +109 -0
  41. data/lib/makara/pool.rb +188 -0
  42. data/lib/makara/proxy.rb +277 -0
  43. data/lib/makara/railtie.rb +14 -0
  44. data/lib/makara/version.rb +15 -0
  45. data/makara.gemspec +19 -0
  46. data/spec/active_record/connection_adapters/makara_abstract_adapter_error_handling_spec.rb +92 -0
  47. data/spec/active_record/connection_adapters/makara_abstract_adapter_spec.rb +114 -0
  48. data/spec/active_record/connection_adapters/makara_mysql2_adapter_spec.rb +183 -0
  49. data/spec/active_record/connection_adapters/makara_postgresql_adapter_spec.rb +121 -0
  50. data/spec/cache_spec.rb +59 -0
  51. data/spec/config_parser_spec.rb +102 -0
  52. data/spec/connection_wrapper_spec.rb +33 -0
  53. data/spec/context_spec.rb +107 -0
  54. data/spec/middleware_spec.rb +84 -0
  55. data/spec/pool_spec.rb +158 -0
  56. data/spec/proxy_spec.rb +182 -0
  57. data/spec/spec_helper.rb +46 -0
  58. data/spec/support/configurator.rb +13 -0
  59. data/spec/support/deep_dup.rb +12 -0
  60. data/spec/support/mock_objects.rb +67 -0
  61. data/spec/support/mysql2_database.yml +17 -0
  62. data/spec/support/mysql2_database_with_custom_errors.yml +17 -0
  63. data/spec/support/pool_extensions.rb +14 -0
  64. data/spec/support/postgresql_database.yml +13 -0
  65. data/spec/support/proxy_extensions.rb +33 -0
  66. data/spec/support/schema.rb +7 -0
  67. 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
@@ -0,0 +1,15 @@
1
+ module Makara
2
+ module VERSION
3
+
4
+ MAJOR = 0
5
+ MINOR = 3
6
+ PATCH = 5
7
+ PRE = nil
8
+
9
+ def self.to_s
10
+ [MAJOR, MINOR, PATCH, PRE].compact.join('.')
11
+ end
12
+
13
+ end unless defined?(::Makara::VERSION)
14
+ ::Makara::VERSION
15
+ end
@@ -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