464 lines
17 KiB
Ruby
464 lines
17 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "cases/helper"
|
|
require "models/person"
|
|
|
|
module ActiveRecord
|
|
module ConnectionAdapters
|
|
class ConnectionHandlerTest < ActiveRecord::TestCase
|
|
fixtures :people
|
|
|
|
def setup
|
|
@handler = ConnectionHandler.new
|
|
@connection_name = "ActiveRecord::Base"
|
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
@pool = @handler.establish_connection(db_config)
|
|
end
|
|
|
|
def teardown
|
|
clean_up_connection_handler
|
|
end
|
|
|
|
def test_default_env_fall_back_to_default_env_when_rails_env_or_rack_env_is_empty_string
|
|
original_rails_env = ENV["RAILS_ENV"]
|
|
original_rack_env = ENV["RACK_ENV"]
|
|
ENV["RAILS_ENV"] = ENV["RACK_ENV"] = ""
|
|
|
|
assert_equal "default_env", ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
|
|
ensure
|
|
ENV["RAILS_ENV"] = original_rails_env
|
|
ENV["RACK_ENV"] = original_rack_env
|
|
end
|
|
|
|
def test_establish_connection_using_3_levels_config
|
|
previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
|
|
|
|
config = {
|
|
"default_env" => {
|
|
"readonly" => { "adapter" => "sqlite3", "database" => "test/db/readonly.sqlite3" },
|
|
"primary" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" }
|
|
},
|
|
"another_env" => {
|
|
"readonly" => { "adapter" => "sqlite3", "database" => "test/db/bad-readonly.sqlite3" },
|
|
"primary" => { "adapter" => "sqlite3", "database" => "test/db/bad-primary.sqlite3" }
|
|
},
|
|
"common" => { "adapter" => "sqlite3", "database" => "test/db/common.sqlite3" }
|
|
}
|
|
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
|
|
|
|
@handler.establish_connection(:common)
|
|
@handler.establish_connection(:primary)
|
|
@handler.establish_connection(:readonly)
|
|
|
|
assert_not_nil pool = @handler.retrieve_connection_pool("readonly")
|
|
assert_equal "test/db/readonly.sqlite3", pool.db_config.database
|
|
|
|
assert_not_nil pool = @handler.retrieve_connection_pool("primary")
|
|
assert_equal "test/db/primary.sqlite3", pool.db_config.database
|
|
|
|
assert_not_nil pool = @handler.retrieve_connection_pool("common")
|
|
assert_equal "test/db/common.sqlite3", pool.db_config.database
|
|
ensure
|
|
ActiveRecord::Base.configurations = @prev_configs
|
|
ENV["RAILS_ENV"] = previous_env
|
|
end
|
|
|
|
unless in_memory_db?
|
|
def test_not_setting_writing_role_while_using_another_named_role_raises
|
|
connection_handler = ActiveRecord::Base.connection_handler
|
|
ActiveRecord::Base.connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
|
|
|
ActiveRecord::Base.connects_to(shards: { default: { also_writing: :arunit }, one: { also_writing: :arunit } })
|
|
|
|
assert_raises(ArgumentError) { setup_shared_connection_pool }
|
|
ensure
|
|
ActiveRecord::Base.connection_handler = connection_handler
|
|
ActiveRecord::Base.establish_connection :arunit
|
|
end
|
|
|
|
def test_fixtures_dont_raise_if_theres_no_writing_pool_config
|
|
connection_handler = ActiveRecord::Base.connection_handler
|
|
ActiveRecord::Base.connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
|
|
|
assert_nothing_raised do
|
|
ActiveRecord::Base.connects_to(database: { reading: :arunit, writing: :arunit })
|
|
end
|
|
|
|
rw_conn = ActiveRecord::Base.connection_handler.retrieve_connection("ActiveRecord::Base", role: :writing)
|
|
ro_conn = ActiveRecord::Base.connection_handler.retrieve_connection("ActiveRecord::Base", role: :reading)
|
|
|
|
assert_equal rw_conn, ro_conn
|
|
ensure
|
|
ActiveRecord::Base.connection_handler = connection_handler
|
|
ActiveRecord::Base.establish_connection :arunit
|
|
end
|
|
|
|
def test_setting_writing_role_while_using_another_named_role_does_not_raise
|
|
connection_handler = ActiveRecord::Base.connection_handler
|
|
ActiveRecord::Base.connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
|
old_role, ActiveRecord.writing_role = ActiveRecord.writing_role, :also_writing
|
|
|
|
ActiveRecord::Base.connects_to(shards: { default: { also_writing: :arunit }, one: { also_writing: :arunit } })
|
|
|
|
assert_nothing_raised { setup_shared_connection_pool }
|
|
ensure
|
|
ActiveRecord.writing_role = old_role
|
|
ActiveRecord::Base.connection_handler = connection_handler
|
|
ActiveRecord::Base.establish_connection :arunit
|
|
end
|
|
|
|
def test_establish_connection_with_primary_works_without_deprecation
|
|
old_config = ActiveRecord::Base.configurations
|
|
config = { "primary" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" } }
|
|
ActiveRecord::Base.configurations = config
|
|
|
|
@handler.establish_connection(:primary)
|
|
|
|
assert_not_deprecated(ActiveRecord.deprecator) do
|
|
@handler.retrieve_connection("primary")
|
|
@handler.remove_connection_pool("primary")
|
|
end
|
|
ensure
|
|
ActiveRecord::Base.configurations = old_config
|
|
end
|
|
|
|
def test_establish_connection_using_3_level_config_defaults_to_default_env_primary_db
|
|
previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
|
|
|
|
config = {
|
|
"default_env" => {
|
|
"primary" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" },
|
|
"readonly" => { "adapter" => "sqlite3", "database" => "test/db/readonly.sqlite3" }
|
|
},
|
|
"another_env" => {
|
|
"primary" => { "adapter" => "sqlite3", "database" => "test/db/another-primary.sqlite3" },
|
|
"readonly" => { "adapter" => "sqlite3", "database" => "test/db/another-readonly.sqlite3" }
|
|
}
|
|
}
|
|
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
|
|
|
|
ActiveRecord::Base.establish_connection
|
|
|
|
assert_match "test/db/primary.sqlite3", ActiveRecord::Base.connection.pool.db_config.database
|
|
ensure
|
|
ActiveRecord::Base.configurations = @prev_configs
|
|
ENV["RAILS_ENV"] = previous_env
|
|
ActiveRecord::Base.establish_connection(:arunit)
|
|
end
|
|
|
|
def test_establish_connection_using_2_level_config_defaults_to_default_env_primary_db
|
|
previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
|
|
|
|
config = {
|
|
"default_env" => {
|
|
"adapter" => "sqlite3", "database" => "test/db/primary.sqlite3"
|
|
},
|
|
"another_env" => {
|
|
"adapter" => "sqlite3", "database" => "test/db/bad-primary.sqlite3"
|
|
}
|
|
}
|
|
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
|
|
|
|
ActiveRecord::Base.establish_connection
|
|
|
|
assert_match "test/db/primary.sqlite3", ActiveRecord::Base.connection.pool.db_config.database
|
|
ensure
|
|
ActiveRecord::Base.configurations = @prev_configs
|
|
ENV["RAILS_ENV"] = previous_env
|
|
ActiveRecord::Base.establish_connection(:arunit)
|
|
end
|
|
end
|
|
|
|
def test_establish_connection_using_two_level_configurations
|
|
config = { "development" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" } }
|
|
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
|
|
|
|
@handler.establish_connection(:development)
|
|
|
|
assert_not_nil pool = @handler.retrieve_connection_pool("development")
|
|
assert_not_predicate pool.connection, :preventing_writes?
|
|
assert_equal "test/db/primary.sqlite3", pool.db_config.database
|
|
ensure
|
|
ActiveRecord::Base.configurations = @prev_configs
|
|
end
|
|
|
|
def test_establish_connection_using_top_level_key_in_two_level_config
|
|
config = {
|
|
"development" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" },
|
|
"development_readonly" => { "adapter" => "sqlite3", "database" => "test/db/readonly.sqlite3" }
|
|
}
|
|
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
|
|
|
|
@handler.establish_connection(:development_readonly)
|
|
|
|
assert_not_nil pool = @handler.retrieve_connection_pool("development_readonly")
|
|
assert_not_predicate pool.connection, :preventing_writes?
|
|
assert_equal "test/db/readonly.sqlite3", pool.db_config.database
|
|
ensure
|
|
ActiveRecord::Base.configurations = @prev_configs
|
|
end
|
|
|
|
def test_symbolized_configurations_assignment
|
|
@prev_configs = ActiveRecord::Base.configurations
|
|
config = {
|
|
development: {
|
|
primary: {
|
|
adapter: "sqlite3",
|
|
database: "test/db/development.sqlite3",
|
|
},
|
|
},
|
|
test: {
|
|
primary: {
|
|
adapter: "sqlite3",
|
|
database: "test/db/test.sqlite3",
|
|
},
|
|
},
|
|
}
|
|
ActiveRecord::Base.configurations = config
|
|
ActiveRecord::Base.configurations.configs_for.each do |db_config|
|
|
assert_instance_of ActiveRecord::DatabaseConfigurations::HashConfig, db_config
|
|
assert_instance_of String, db_config.env_name
|
|
assert_instance_of String, db_config.name
|
|
|
|
db_config.configuration_hash.keys.each do |key|
|
|
assert_instance_of Symbol, key
|
|
end
|
|
end
|
|
ensure
|
|
ActiveRecord::Base.configurations = @prev_configs
|
|
end
|
|
|
|
def test_retrieve_connection
|
|
assert @handler.retrieve_connection(@connection_name)
|
|
end
|
|
|
|
def test_active_connections?
|
|
assert_not @handler.active_connections?(:all)
|
|
assert @handler.retrieve_connection(@connection_name)
|
|
assert @handler.active_connections?(:all)
|
|
@handler.clear_active_connections!(:all)
|
|
assert_not @handler.active_connections?(:all)
|
|
end
|
|
|
|
def test_retrieve_connection_pool
|
|
assert_not_nil @handler.retrieve_connection_pool(@connection_name)
|
|
end
|
|
|
|
def test_retrieve_connection_pool_with_invalid_id
|
|
assert_nil @handler.retrieve_connection_pool("foo")
|
|
end
|
|
|
|
def test_connection_pools
|
|
assert_equal([@pool], @handler.connection_pools)
|
|
end
|
|
|
|
def test_a_class_using_custom_pool_and_switching_back_to_primary
|
|
klass2 = Class.new(Base) { def self.name; "klass2"; end }
|
|
|
|
assert_same klass2.connection, ActiveRecord::Base.connection
|
|
|
|
pool = klass2.establish_connection(ActiveRecord::Base.connection_pool.db_config.configuration_hash)
|
|
assert_same klass2.connection, pool.connection
|
|
assert_not_same klass2.connection, ActiveRecord::Base.connection
|
|
|
|
klass2.remove_connection
|
|
|
|
assert_same klass2.connection, ActiveRecord::Base.connection
|
|
end
|
|
|
|
class ApplicationRecord < ActiveRecord::Base
|
|
self.abstract_class = true
|
|
end
|
|
|
|
class MyClass < ApplicationRecord
|
|
end
|
|
|
|
def test_connection_specification_name_should_fallback_to_parent
|
|
Object.const_set :ApplicationRecord, ApplicationRecord
|
|
|
|
klassA = Class.new(Base)
|
|
klassB = Class.new(klassA)
|
|
klassC = Class.new(MyClass)
|
|
|
|
assert_equal klassB.connection_specification_name, klassA.connection_specification_name
|
|
assert_equal klassC.connection_specification_name, klassA.connection_specification_name
|
|
|
|
assert_equal "ActiveRecord::Base", klassA.connection_specification_name
|
|
assert_equal "ActiveRecord::Base", klassC.connection_specification_name
|
|
|
|
klassA.connection_specification_name = "readonly"
|
|
assert_equal "readonly", klassB.connection_specification_name
|
|
|
|
ActiveRecord::Base.connection_specification_name = "readonly"
|
|
assert_equal "readonly", klassC.connection_specification_name
|
|
ensure
|
|
ApplicationRecord.remove_connection
|
|
Object.send :remove_const, :ApplicationRecord
|
|
ActiveRecord::Base.connection_specification_name = "ActiveRecord::Base"
|
|
end
|
|
|
|
def test_remove_connection_should_not_remove_parent
|
|
klass2 = Class.new(Base) { def self.name; "klass2"; end }
|
|
klass2.remove_connection
|
|
assert_not_nil ActiveRecord::Base.connection
|
|
assert_same klass2.connection, ActiveRecord::Base.connection
|
|
end
|
|
|
|
def test_default_handlers_are_writing_and_reading
|
|
assert_equal :writing, ActiveRecord.writing_role
|
|
assert_equal :reading, ActiveRecord.reading_role
|
|
end
|
|
|
|
if Process.respond_to?(:fork)
|
|
def test_connection_pool_per_pid
|
|
object_id = ActiveRecord::Base.connection.object_id
|
|
|
|
rd, wr = IO.pipe
|
|
rd.binmode
|
|
wr.binmode
|
|
|
|
pid = fork {
|
|
rd.close
|
|
wr.write Marshal.dump ActiveRecord::Base.connection.object_id
|
|
wr.close
|
|
exit!
|
|
}
|
|
|
|
wr.close
|
|
|
|
Process.waitpid pid
|
|
assert_not_equal object_id, Marshal.load(rd.read)
|
|
rd.close
|
|
end
|
|
|
|
def test_forked_child_doesnt_mangle_parent_connection
|
|
object_id = ActiveRecord::Base.connection.object_id
|
|
assert_predicate ActiveRecord::Base.connection, :active?
|
|
|
|
rd, wr = IO.pipe
|
|
rd.binmode
|
|
wr.binmode
|
|
|
|
pid = fork {
|
|
rd.close
|
|
if ActiveRecord::Base.connection.active?
|
|
wr.write Marshal.dump ActiveRecord::Base.connection.object_id
|
|
end
|
|
wr.close
|
|
|
|
exit # allow finalizers to run
|
|
}
|
|
|
|
wr.close
|
|
|
|
Process.waitpid pid
|
|
assert_not_equal object_id, Marshal.load(rd.read)
|
|
rd.close
|
|
|
|
assert_equal 3, ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people")
|
|
end
|
|
|
|
unless in_memory_db?
|
|
def test_forked_child_recovers_from_disconnected_parent
|
|
object_id = ActiveRecord::Base.connection.object_id
|
|
assert_predicate ActiveRecord::Base.connection, :active?
|
|
|
|
rd, wr = IO.pipe
|
|
rd.binmode
|
|
wr.binmode
|
|
|
|
outer_pid = fork {
|
|
ActiveRecord::Base.connection.disconnect!
|
|
|
|
pid = fork {
|
|
rd.close
|
|
if ActiveRecord::Base.connection.active?
|
|
pair = [ActiveRecord::Base.connection.object_id,
|
|
ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people")]
|
|
wr.write Marshal.dump pair
|
|
end
|
|
wr.close
|
|
|
|
exit # allow finalizers to run
|
|
}
|
|
|
|
Process.waitpid pid
|
|
}
|
|
|
|
wr.close
|
|
|
|
Process.waitpid outer_pid
|
|
child_id, child_count = Marshal.load(rd.read)
|
|
|
|
assert_not_equal object_id, child_id
|
|
rd.close
|
|
|
|
assert_equal 3, child_count
|
|
|
|
# Outer connection is unaffected
|
|
assert_equal 6, ActiveRecord::Base.connection.select_value("SELECT 2 * COUNT(*) FROM people")
|
|
end
|
|
end
|
|
|
|
def test_retrieve_connection_pool_copies_schema_cache_from_ancestor_pool
|
|
@pool.schema_cache = @pool.connection.schema_cache
|
|
@pool.schema_cache.add("posts")
|
|
|
|
rd, wr = IO.pipe
|
|
rd.binmode
|
|
wr.binmode
|
|
|
|
pid = fork {
|
|
rd.close
|
|
pool = @handler.retrieve_connection_pool(@connection_name)
|
|
wr.write Marshal.dump pool.schema_cache.size
|
|
wr.close
|
|
exit!
|
|
}
|
|
|
|
wr.close
|
|
|
|
Process.waitpid pid
|
|
assert_equal @pool.schema_cache.size, Marshal.load(rd.read)
|
|
rd.close
|
|
end
|
|
|
|
if current_adapter?(:SQLite3Adapter)
|
|
def test_pool_from_any_process_for_uses_most_recent_spec
|
|
file = Tempfile.new "lol.sqlite3"
|
|
|
|
rd, wr = IO.pipe
|
|
rd.binmode
|
|
wr.binmode
|
|
|
|
pid = fork do
|
|
config_hash = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary").configuration_hash.merge(database: file.path)
|
|
ActiveRecord::Base.establish_connection(config_hash)
|
|
|
|
pid2 = fork do
|
|
wr.write ActiveRecord::Base.connection_db_config.database
|
|
wr.close
|
|
end
|
|
|
|
Process.waitpid pid2
|
|
end
|
|
|
|
Process.waitpid pid
|
|
|
|
wr.close
|
|
|
|
assert_equal file.path, rd.read
|
|
|
|
rd.close
|
|
ensure
|
|
if file
|
|
file.close
|
|
file.unlink
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|