active_record_host_pool 1.2.4 → 2.0.0.pre.2
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 +4 -4
- data/Changelog.md +112 -0
- data/Readme.md +3 -3
- data/lib/active_record_host_pool/clear_query_cache_patch.rb +13 -14
- data/lib/active_record_host_pool/connection_adapter_mixin.rb +50 -44
- data/lib/active_record_host_pool/connection_proxy.rb +3 -1
- data/lib/active_record_host_pool/pool_proxy.rb +182 -5
- data/lib/active_record_host_pool/version.rb +1 -1
- data/lib/active_record_host_pool.rb +21 -9
- metadata +11 -160
- data/lib/active_record_host_pool/pool_proxy_6_1.rb +0 -154
- data/lib/active_record_host_pool/pool_proxy_legacy.rb +0 -156
- data/test/database.yml +0 -108
- data/test/helper.rb +0 -198
- data/test/schema.rb +0 -23
- data/test/test_arhp.rb +0 -186
data/test/helper.rb
DELETED
@@ -1,198 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'bundler/setup'
|
4
|
-
require 'minitest/autorun'
|
5
|
-
require 'pry-byebug'
|
6
|
-
|
7
|
-
require 'active_record_host_pool'
|
8
|
-
require 'logger'
|
9
|
-
require 'minitest/mock_expectations'
|
10
|
-
require 'phenix'
|
11
|
-
|
12
|
-
ENV['RAILS_ENV'] = 'test'
|
13
|
-
ENV['LEGACY_CONNECTION_HANDLING'] = 'true' if ENV['LEGACY_CONNECTION_HANDLING'].nil?
|
14
|
-
|
15
|
-
if ActiveRecord.version >= Gem::Version.new('6.1')
|
16
|
-
ActiveRecord::Base.legacy_connection_handling = (ENV['LEGACY_CONNECTION_HANDLING'] == 'true')
|
17
|
-
end
|
18
|
-
|
19
|
-
RAILS_6_1_WITH_NON_LEGACY_CONNECTION_HANDLING =
|
20
|
-
ActiveRecord.version >= Gem::Version.new('6.1') && !ActiveRecord::Base.legacy_connection_handling
|
21
|
-
|
22
|
-
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/test.log')
|
23
|
-
|
24
|
-
Thread.abort_on_exception = true
|
25
|
-
|
26
|
-
# BEGIN preventing_writes? patch
|
27
|
-
## Rails 6.1 by default does not allow writing to replica databases which prevents
|
28
|
-
## us from properly setting up the test databases. This patch is used in test/schema.rb
|
29
|
-
## to allow us to write to the replicas but only during migrations
|
30
|
-
module ActiveRecordHostPool
|
31
|
-
cattr_accessor :allowing_writes
|
32
|
-
module PreventWritesPatch
|
33
|
-
def preventing_writes?
|
34
|
-
return false if ActiveRecordHostPool.allowing_writes && replica?
|
35
|
-
|
36
|
-
super
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
if ActiveRecord.version >= Gem::Version.new('6.1')
|
42
|
-
ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend ActiveRecordHostPool::PreventWritesPatch
|
43
|
-
end
|
44
|
-
# END preventing_writes? patch
|
45
|
-
|
46
|
-
Phenix.configure do |config|
|
47
|
-
config.skip_database = ->(name, conf) { name =~ /not_there/ || conf['username'] == 'john-doe' }
|
48
|
-
end
|
49
|
-
|
50
|
-
module ARHPTestSetup
|
51
|
-
private
|
52
|
-
|
53
|
-
def arhp_create_models
|
54
|
-
return if ARHPTestSetup.const_defined?('Pool1DbA')
|
55
|
-
|
56
|
-
if RAILS_6_1_WITH_NON_LEGACY_CONNECTION_HANDLING
|
57
|
-
eval <<-RUBY
|
58
|
-
class AbstractPool1DbC < ActiveRecord::Base
|
59
|
-
self.abstract_class = true
|
60
|
-
connects_to database: { writing: :test_pool_1_db_c }
|
61
|
-
end
|
62
|
-
|
63
|
-
# The placement of the Pool1DbC class is important so that its
|
64
|
-
# connection will not be the most recent connection established
|
65
|
-
# for test_pool_1.
|
66
|
-
class Pool1DbC < AbstractPool1DbC
|
67
|
-
end
|
68
|
-
|
69
|
-
class AbstractPool1DbA < ActiveRecord::Base
|
70
|
-
self.abstract_class = true
|
71
|
-
connects_to database: { writing: :test_pool_1_db_a, reading: :test_pool_1_db_a_replica }
|
72
|
-
end
|
73
|
-
|
74
|
-
class Pool1DbA < AbstractPool1DbA
|
75
|
-
self.table_name = "tests"
|
76
|
-
end
|
77
|
-
|
78
|
-
class AbstractPool1DbB < ActiveRecord::Base
|
79
|
-
self.abstract_class = true
|
80
|
-
connects_to database: { writing: :test_pool_1_db_b }
|
81
|
-
end
|
82
|
-
|
83
|
-
class Pool1DbB < AbstractPool1DbB
|
84
|
-
self.table_name = "tests"
|
85
|
-
end
|
86
|
-
|
87
|
-
class AbstractPool2DbD < ActiveRecord::Base
|
88
|
-
self.abstract_class = true
|
89
|
-
connects_to database: { writing: :test_pool_2_db_d }
|
90
|
-
end
|
91
|
-
|
92
|
-
class Pool2DbD < AbstractPool2DbD
|
93
|
-
self.table_name = "tests"
|
94
|
-
end
|
95
|
-
|
96
|
-
class AbstractPool2DbE < ActiveRecord::Base
|
97
|
-
self.abstract_class = true
|
98
|
-
connects_to database: { writing: :test_pool_2_db_e }
|
99
|
-
end
|
100
|
-
|
101
|
-
class Pool2DbE < AbstractPool2DbE
|
102
|
-
self.table_name = "tests"
|
103
|
-
end
|
104
|
-
|
105
|
-
class AbstractPool3DbE < ActiveRecord::Base
|
106
|
-
self.abstract_class = true
|
107
|
-
connects_to database: { writing: :test_pool_3_db_e }
|
108
|
-
end
|
109
|
-
|
110
|
-
class Pool3DbE < AbstractPool3DbE
|
111
|
-
self.table_name = "tests"
|
112
|
-
end
|
113
|
-
|
114
|
-
# Test ARHP with Rails 6.1+ horizontal sharding functionality
|
115
|
-
class AbstractShardedModel < ActiveRecord::Base
|
116
|
-
self.abstract_class = true
|
117
|
-
connects_to shards: {
|
118
|
-
default: { writing: :test_pool_1_db_shard_a },
|
119
|
-
shard_b: { writing: :test_pool_1_db_shard_b, reading: :test_pool_1_db_shard_b_replica },
|
120
|
-
shard_c: { writing: :test_pool_1_db_shard_c, reading: :test_pool_1_db_shard_c_replica },
|
121
|
-
shard_d: { writing: :test_pool_2_db_shard_d, reading: :test_pool_2_db_shard_d_replica }
|
122
|
-
}
|
123
|
-
end
|
124
|
-
|
125
|
-
class ShardedModel < AbstractShardedModel
|
126
|
-
self.table_name = "tests"
|
127
|
-
end
|
128
|
-
RUBY
|
129
|
-
else
|
130
|
-
eval <<-RUBY
|
131
|
-
# The placement of the Pool1DbC class is important so that its
|
132
|
-
# connection will not be the most recent connection established
|
133
|
-
# for test_pool_1.
|
134
|
-
class Pool1DbC < ActiveRecord::Base
|
135
|
-
establish_connection(:test_pool_1_db_c)
|
136
|
-
end
|
137
|
-
|
138
|
-
class Pool1DbA < ActiveRecord::Base
|
139
|
-
self.table_name = "tests"
|
140
|
-
establish_connection(:test_pool_1_db_a)
|
141
|
-
end
|
142
|
-
|
143
|
-
class Pool1DbAReplica < ActiveRecord::Base
|
144
|
-
self.table_name = "tests"
|
145
|
-
establish_connection(:test_pool_1_db_a_replica)
|
146
|
-
end
|
147
|
-
|
148
|
-
class Pool1DbB < ActiveRecord::Base
|
149
|
-
self.table_name = "tests"
|
150
|
-
establish_connection(:test_pool_1_db_b)
|
151
|
-
end
|
152
|
-
|
153
|
-
class Pool2DbD < ActiveRecord::Base
|
154
|
-
self.table_name = "tests"
|
155
|
-
establish_connection(:test_pool_2_db_d)
|
156
|
-
end
|
157
|
-
|
158
|
-
class Pool2DbE < ActiveRecord::Base
|
159
|
-
self.table_name = "tests"
|
160
|
-
establish_connection(:test_pool_2_db_e)
|
161
|
-
end
|
162
|
-
|
163
|
-
class Pool3DbE < ActiveRecord::Base
|
164
|
-
self.table_name = "tests"
|
165
|
-
establish_connection(:test_pool_3_db_e)
|
166
|
-
end
|
167
|
-
RUBY
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def current_database(klass)
|
172
|
-
klass.connection.select_value('select DATABASE()')
|
173
|
-
end
|
174
|
-
|
175
|
-
# Remove a method from a given module that fixes something.
|
176
|
-
# Execute the passed in block.
|
177
|
-
# Re-add the method back to the module.
|
178
|
-
def without_module_patch(mod, method_name)
|
179
|
-
method_body = mod.instance_method(method_name)
|
180
|
-
mod.remove_method(method_name)
|
181
|
-
yield if block_given?
|
182
|
-
ensure
|
183
|
-
mod.define_method(method_name, method_body)
|
184
|
-
end
|
185
|
-
|
186
|
-
def simulate_rails_app_active_record_railties
|
187
|
-
if ActiveRecord.version >= Gem::Version.new('6.0') && !RAILS_6_1_WITH_NON_LEGACY_CONNECTION_HANDLING
|
188
|
-
# Necessary for testing ActiveRecord 6.0 which uses the connection
|
189
|
-
# handlers when clearing query caches across all handlers when
|
190
|
-
# an operation that dirties the cache is involved (e.g. create/insert,
|
191
|
-
# update, delete/destroy, truncate, etc.)
|
192
|
-
# In Rails 6.1 this is only present when legacy_connection_handling=true
|
193
|
-
ActiveRecord::Base.connection_handlers = {
|
194
|
-
ActiveRecord::Base.writing_role => ActiveRecord::Base.default_connection_handler
|
195
|
-
}
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
data/test/schema.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'helper'
|
4
|
-
|
5
|
-
begin
|
6
|
-
ActiveRecordHostPool.allowing_writes = true
|
7
|
-
|
8
|
-
ActiveRecord::Schema.define(version: 1) do
|
9
|
-
create_table 'tests', force: true do |t|
|
10
|
-
t.string 'val'
|
11
|
-
end
|
12
|
-
|
13
|
-
# Add a table only the shard database will have. Conditional
|
14
|
-
# exists since Phenix loads the schema file for every database.
|
15
|
-
if ActiveRecord::Base.connection.current_database == 'arhp_test_db_c'
|
16
|
-
create_table 'pool1_db_cs' do |t|
|
17
|
-
t.string 'name'
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
ensure
|
22
|
-
ActiveRecordHostPool.allowing_writes = false
|
23
|
-
end
|
data/test/test_arhp.rb
DELETED
@@ -1,186 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'helper'
|
4
|
-
|
5
|
-
class ActiveRecordHostPoolTest < Minitest::Test
|
6
|
-
include ARHPTestSetup
|
7
|
-
def setup
|
8
|
-
if RAILS_6_1_WITH_NON_LEGACY_CONNECTION_HANDLING
|
9
|
-
Phenix.rise! config_path: 'test/three_tier_database.yml'
|
10
|
-
else
|
11
|
-
Phenix.rise!
|
12
|
-
end
|
13
|
-
arhp_create_models
|
14
|
-
end
|
15
|
-
|
16
|
-
def teardown
|
17
|
-
ActiveRecord::Base.connection.disconnect!
|
18
|
-
ActiveRecordHostPool::PoolProxy.class_variable_set(:@@_connection_pools, {})
|
19
|
-
Phenix.burn!
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_process_forking_with_connections
|
23
|
-
# Ensure we have a connection already
|
24
|
-
assert_equal(true, ActiveRecord::Base.connected?)
|
25
|
-
|
26
|
-
# Verify that when we fork, the process doesn't crash
|
27
|
-
pid = Process.fork do
|
28
|
-
if ActiveRecord.version >= Gem::Version.new('5.2')
|
29
|
-
assert_equal(false, ActiveRecord::Base.connected?) # New to Rails 5.2
|
30
|
-
else
|
31
|
-
assert_equal(true, ActiveRecord::Base.connected?)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
Process.wait(pid)
|
35
|
-
# Cleanup any connections we may have left around
|
36
|
-
ActiveRecord::Base.connection_handler.clear_all_connections!
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_models_with_matching_hosts_ports_sockets_usernames_and_replica_status_should_share_a_connection
|
40
|
-
assert_equal(Pool1DbA.connection.raw_connection, Pool1DbB.connection.raw_connection)
|
41
|
-
assert_equal(Pool2DbD.connection.raw_connection, Pool2DbE.connection.raw_connection)
|
42
|
-
end
|
43
|
-
|
44
|
-
def test_models_with_different_ports_should_not_share_a_connection
|
45
|
-
refute_equal(Pool1DbA.connection.raw_connection, Pool2DbD.connection.raw_connection)
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_models_with_different_usernames_should_not_share_a_connection
|
49
|
-
refute_equal(Pool2DbE.connection.raw_connection, Pool3DbE.connection.raw_connection)
|
50
|
-
end
|
51
|
-
|
52
|
-
def test_should_select_on_correct_database
|
53
|
-
Pool1DbA.connection.send(:select_all, 'select 1')
|
54
|
-
assert_equal 'arhp_test_db_a', current_database(Pool1DbA)
|
55
|
-
|
56
|
-
Pool2DbD.connection.send(:select_all, 'select 1')
|
57
|
-
assert_equal 'arhp_test_db_d', current_database(Pool2DbD)
|
58
|
-
|
59
|
-
Pool3DbE.connection.send(:select_all, 'select 1')
|
60
|
-
assert_equal 'arhp_test_db_e', current_database(Pool3DbE)
|
61
|
-
end
|
62
|
-
|
63
|
-
def test_should_insert_on_correct_database
|
64
|
-
Pool1DbA.connection.send(:insert, "insert into tests values(NULL, 'foo')")
|
65
|
-
assert_equal 'arhp_test_db_a', current_database(Pool1DbA)
|
66
|
-
|
67
|
-
Pool2DbD.connection.send(:insert, "insert into tests values(NULL, 'foo')")
|
68
|
-
assert_equal 'arhp_test_db_d', current_database(Pool2DbD)
|
69
|
-
|
70
|
-
Pool3DbE.connection.send(:insert, "insert into tests values(NULL, 'foo')")
|
71
|
-
assert_equal 'arhp_test_db_e', current_database(Pool3DbE)
|
72
|
-
end
|
73
|
-
|
74
|
-
def test_connection_returns_a_proxy
|
75
|
-
assert_kind_of ActiveRecordHostPool::ConnectionProxy, Pool1DbA.connection
|
76
|
-
end
|
77
|
-
|
78
|
-
def test_connection_proxy_handles_private_methods
|
79
|
-
# Relies on connection.class returning the real class
|
80
|
-
Pool1DbA.connection.class.class_eval do
|
81
|
-
private
|
82
|
-
|
83
|
-
def test_private_method
|
84
|
-
true
|
85
|
-
end
|
86
|
-
end
|
87
|
-
assert Pool1DbA.connection.respond_to?(:test_private_method, true)
|
88
|
-
refute Pool1DbA.connection.respond_to?(:test_private_method)
|
89
|
-
assert_includes(Pool1DbA.connection.private_methods, :test_private_method)
|
90
|
-
assert_equal true, Pool1DbA.connection.send(:test_private_method)
|
91
|
-
end
|
92
|
-
|
93
|
-
def test_object_creation
|
94
|
-
Pool1DbA.create(val: 'foo')
|
95
|
-
assert_equal('arhp_test_db_a', current_database(Pool1DbA))
|
96
|
-
|
97
|
-
Pool2DbD.create(val: 'bar')
|
98
|
-
assert_equal('arhp_test_db_a', current_database(Pool1DbA))
|
99
|
-
assert_equal('arhp_test_db_d', current_database(Pool2DbD))
|
100
|
-
|
101
|
-
Pool1DbB.create!(val: 'bar_distinct')
|
102
|
-
assert_equal('arhp_test_db_b', current_database(Pool1DbB))
|
103
|
-
assert Pool1DbB.find_by_val('bar_distinct')
|
104
|
-
refute Pool1DbA.find_by_val('bar_distinct')
|
105
|
-
end
|
106
|
-
|
107
|
-
def test_disconnect
|
108
|
-
Pool1DbA.create(val: 'foo')
|
109
|
-
unproxied = Pool1DbA.connection.unproxied
|
110
|
-
Pool1DbA.connection_handler.clear_all_connections!
|
111
|
-
Pool1DbA.create(val: 'foo')
|
112
|
-
assert(unproxied != Pool1DbA.connection.unproxied)
|
113
|
-
end
|
114
|
-
|
115
|
-
def test_checkout
|
116
|
-
connection = ActiveRecord::Base.connection_pool.checkout
|
117
|
-
assert_kind_of(ActiveRecordHostPool::ConnectionProxy, connection)
|
118
|
-
ActiveRecord::Base.connection_pool.checkin(connection)
|
119
|
-
c2 = ActiveRecord::Base.connection_pool.checkout
|
120
|
-
assert(c2 == connection)
|
121
|
-
end
|
122
|
-
|
123
|
-
def test_no_switch_when_creating_db
|
124
|
-
conn = Pool1DbA.connection
|
125
|
-
assert_called(conn, :execute_without_switching) do
|
126
|
-
refute_called(conn, :_switch_connection) do
|
127
|
-
assert conn._host_pool_current_database
|
128
|
-
conn.create_database(:some_args)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def test_no_switch_when_dropping_db
|
134
|
-
conn = Pool1DbA.connection
|
135
|
-
assert_called(conn, :execute_without_switching) do
|
136
|
-
refute_called(conn, :_switch_connection) do
|
137
|
-
assert conn._host_pool_current_database
|
138
|
-
conn.drop_database(:some_args)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def test_underlying_assumption_about_test_db
|
144
|
-
debug_me = false
|
145
|
-
# ensure connection
|
146
|
-
Pool1DbA.first
|
147
|
-
|
148
|
-
# which is the "default" DB to connect to?
|
149
|
-
first_db = Pool1DbA.connection.unproxied.instance_variable_get(:@_cached_current_database)
|
150
|
-
puts "\nOk, we started on #{first_db}" if debug_me
|
151
|
-
|
152
|
-
switch_to_klass = case first_db
|
153
|
-
when 'arhp_test_db_b'
|
154
|
-
Pool1DbA
|
155
|
-
when 'arhp_test_db_a'
|
156
|
-
Pool1DbB
|
157
|
-
else
|
158
|
-
raise "Expected a database name, got #{first_db.inspect}"
|
159
|
-
end
|
160
|
-
expected_database = switch_to_klass.connection.instance_variable_get(:@database)
|
161
|
-
|
162
|
-
# switch to the other database
|
163
|
-
switch_to_klass.first
|
164
|
-
puts "\nAnd now we're on #{current_database(switch_to_klass)}" if debug_me
|
165
|
-
|
166
|
-
# get the current thread id so we can shoot ourselves in the head
|
167
|
-
thread_id = switch_to_klass.connection.select_value('select @@pseudo_thread_id')
|
168
|
-
|
169
|
-
# now, disable our auto-switching and trigger a mysql reconnect
|
170
|
-
switch_to_klass.connection.unproxied.stub(:_switch_connection, true) do
|
171
|
-
Pool2DbD.connection.execute("KILL #{thread_id}")
|
172
|
-
end
|
173
|
-
|
174
|
-
# and finally, did mysql reconnect correctly?
|
175
|
-
puts "\nAnd now we end up on #{current_database(switch_to_klass)}" if debug_me
|
176
|
-
assert_equal expected_database, current_database(switch_to_klass)
|
177
|
-
end
|
178
|
-
|
179
|
-
def test_release_connection
|
180
|
-
pool = ActiveRecord::Base.connection_pool
|
181
|
-
conn = pool.connection
|
182
|
-
assert_called_with(pool, :checkin, [conn]) do
|
183
|
-
pool.release_connection
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|