提交 b8fc0150 编写于 作者: E eileencodes 提交者: John Crepezzi

Reduce surface area of ConnectionSpecification

Eventually we'd like to get rid of this class altogether but for now
this PR reduces the surface area by removing methods from the class and
moving classes out into their own files.

* `adapter_method` was moved into database configurations
* `initialize_dup` was removed because it was only used in tests
* Resolver is now it's own class under connection adapters
* ConnectionUrlResolver, only used by the configurations, is in a class
under DatabaseConfigurations
Co-authored-by: NJohn Crepezzi <john.crepezzi@gmail.com>
上级 d4437adc
......@@ -10,6 +10,7 @@ module ConnectionAdapters
autoload :Column
autoload :ConnectionSpecification
autoload :Resolver
autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
autoload :IndexDefinition
......
......@@ -385,14 +385,14 @@ def initialize(spec)
@spec = spec
@checkout_timeout = (spec.underlying_configuration_hash[:checkout_timeout] && spec.underlying_configuration_hash[:checkout_timeout].to_f) || 5
if @idle_timeout = spec.underlying_configuration_hash.fetch(:idle_timeout, 300)
@checkout_timeout = (spec.db_config.configuration_hash[:checkout_timeout] && spec.db_config.configuration_hash[:checkout_timeout].to_f) || 5
if @idle_timeout = spec.db_config.configuration_hash.fetch(:idle_timeout, 300)
@idle_timeout = @idle_timeout.to_f
@idle_timeout = nil if @idle_timeout <= 0
end
# default max pool size to 5
@size = (spec.underlying_configuration_hash[:pool] && spec.underlying_configuration_hash[:pool].to_i) || 5
@size = (spec.db_config.configuration_hash[:pool] && spec.db_config.configuration_hash[:pool].to_i) || 5
# This variable tracks the cache of threads mapped to reserved connections, with the
# sole purpose of speeding up the +connection+ method. It is not the authoritative
......@@ -422,7 +422,7 @@ def initialize(spec)
# +reaping_frequency+ is configurable mostly for historical reasons, but it could
# also be useful if someone wants a very low +idle_timeout+.
reaping_frequency = spec.underlying_configuration_hash.fetch(:reaping_frequency, 60)
reaping_frequency = spec.db_config.configuration_hash.fetch(:reaping_frequency, 60)
@reaper = Reaper.new(self, reaping_frequency && reaping_frequency.to_f)
@reaper.run
end
......@@ -505,7 +505,7 @@ def connections
# Raises:
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
# connections in the pool within a timeout interval (default duration is
# <tt>spec.underlying_configuration_hash[:checkout_timeout] * 2</tt> seconds).
# <tt>spec.db_config.configuration_hash[:checkout_timeout] * 2</tt> seconds).
def disconnect(raise_on_acquisition_timeout = true)
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
synchronize do
......@@ -526,7 +526,7 @@ def disconnect(raise_on_acquisition_timeout = true)
#
# The pool first tries to gain ownership of all connections. If unable to
# do so within a timeout interval (default duration is
# <tt>spec.underlying_configuration_hash[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully
# <tt>spec.db_config.configuration_hash[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully
# disconnected without any regard for other connection owning threads.
def disconnect!
disconnect(false)
......@@ -557,7 +557,7 @@ def discarded? # :nodoc:
# Raises:
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
# connections in the pool within a timeout interval (default duration is
# <tt>spec.underlying_configuration_hash[:checkout_timeout] * 2</tt> seconds).
# <tt>spec.db_config.configuration_hash[:checkout_timeout] * 2</tt> seconds).
def clear_reloadable_connections(raise_on_acquisition_timeout = true)
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
synchronize do
......@@ -579,7 +579,7 @@ def clear_reloadable_connections(raise_on_acquisition_timeout = true)
#
# The pool first tries to gain ownership of all connections. If unable to
# do so within a timeout interval (default duration is
# <tt>spec.underlying_configuration_hash[:checkout_timeout] * 2</tt> seconds), then the pool forcefully
# <tt>spec.db_config.configuration_hash[:checkout_timeout] * 2</tt> seconds), then the pool forcefully
# clears the cache and reloads connections without any regard for other
# connection owning threads.
def clear_reloadable_connections!
......@@ -899,7 +899,7 @@ def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
alias_method :release, :remove_connection_from_thread_cache
def new_connection
Base.send(spec.adapter_method, spec.underlying_configuration_hash).tap do |conn|
Base.send(spec.db_config.adapter_method, spec.db_config.configuration_hash).tap do |conn|
conn.check_version
end
end
......@@ -1063,7 +1063,7 @@ def connection_pool_list
alias :connection_pools :connection_pool_list
def establish_connection(config)
resolver = ConnectionSpecification::Resolver.new(Base.configurations)
resolver = Resolver.new(Base.configurations)
spec = resolver.spec(config)
remove_connection(spec.name)
......@@ -1074,7 +1074,7 @@ def establish_connection(config)
}
if spec
payload[:spec_name] = spec.name
payload[:config] = spec.underlying_configuration_hash
payload[:config] = spec.db_config.configuration_hash
end
message_bus.instrument("!connection.active_record", payload) do
......@@ -1149,7 +1149,7 @@ def remove_connection(spec_name)
if pool = owner_to_pool.delete(spec_name)
pool.automatic_reconnect = false
pool.disconnect!
pool.spec.underlying_configuration_hash
pool.spec.db_config.configuration_hash
end
end
......@@ -1164,7 +1164,7 @@ def retrieve_connection_pool(spec_name)
# A connection was established in an ancestor process that must have
# subsequently forked. We can't reuse the connection, but we can copy
# the specification and establish a new connection with it.
establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
establish_connection(ancestor_pool.spec.db_config.configuration_hash).tap do |pool|
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
end
else
......
......@@ -4,259 +4,11 @@
module ActiveRecord
module ConnectionAdapters
class ConnectionSpecification #:nodoc:
attr_reader :name, :adapter_method, :db_config
class ConnectionSpecification # :nodoc:
attr_reader :name, :db_config
def initialize(name, db_config, adapter_method)
@name, @db_config, @adapter_method = name, db_config, adapter_method
end
def underlying_configuration_hash
@db_config.configuration_hash
end
def initialize_dup(original)
@db_config = original.db_config.dup
end
def to_hash
underlying_configuration_hash.dup.merge(name: @name)
end
# Expands a connection string into a hash.
class ConnectionUrlResolver # :nodoc:
# == Example
#
# url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
# ConnectionUrlResolver.new(url).to_hash
# # => {
# adapter: "postgresql",
# host: "localhost",
# port: 9000,
# database: "foo_test",
# username: "foo",
# password: "bar",
# pool: "5",
# timeout: "3000"
# }
def initialize(url)
raise "Database URL cannot be empty" if url.blank?
@uri = uri_parser.parse(url)
@adapter = @uri.scheme && @uri.scheme.tr("-", "_")
@adapter = "postgresql" if @adapter == "postgres"
if @uri.opaque
@uri.opaque, @query = @uri.opaque.split("?", 2)
else
@query = @uri.query
end
end
# Converts the given URL to a full connection hash.
def to_hash
config = raw_config.compact_blank
config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String }
config
end
private
attr_reader :uri
def uri_parser
@uri_parser ||= URI::Parser.new
end
# Converts the query parameters of the URI into a hash.
#
# "localhost?pool=5&reaping_frequency=2"
# # => { pool: "5", reaping_frequency: "2" }
#
# returns empty hash if no query present.
#
# "localhost"
# # => {}
def query_hash
Hash[(@query || "").split("&").map { |pair| pair.split("=") }].symbolize_keys
end
def raw_config
if uri.opaque
query_hash.merge(
adapter: @adapter,
database: uri.opaque
)
else
query_hash.merge(
adapter: @adapter,
username: uri.user,
password: uri.password,
port: uri.port,
database: database_from_path,
host: uri.hostname
)
end
end
# Returns name of the database.
def database_from_path
if @adapter == "sqlite3"
# 'sqlite3:/foo' is absolute, because that makes sense. The
# corresponding relative version, 'sqlite3:foo', is handled
# elsewhere, as an "opaque".
uri.path
else
# Only SQLite uses a filename as the "database" name; for
# anything else, a leading slash would be silly.
uri.path.sub(%r{^/}, "")
end
end
end
##
# Builds a ConnectionSpecification from user input.
class Resolver # :nodoc:
attr_reader :configurations
# Accepts a list of db config objects.
def initialize(configurations)
@configurations = configurations
end
# Returns an instance of ConnectionSpecification for a given adapter.
# Accepts a hash one layer deep that contains all connection information.
#
# == Example
#
# config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
# spec = Resolver.new(config).spec(:production)
# spec.adapter_method
# # => "sqlite3_connection"
# spec.underlying_configuration_hash
# # => { host: "localhost", database: "foo", adapter: "sqlite3" }
#
def spec(config)
pool_name = config if config.is_a?(Symbol)
db_config = resolve(config, pool_name)
spec = db_config.configuration_hash
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
# Require the adapter itself and give useful feedback about
# 1. Missing adapter gems and
# 2. Adapter gems' missing dependencies.
path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter"
begin
require path_to_adapter
rescue LoadError => e
# We couldn't require the adapter itself. Raise an exception that
# points out config typos and missing gems.
if e.path == path_to_adapter
# We can assume that a non-builtin adapter was specified, so it's
# either misspelled or missing from Gemfile.
raise LoadError, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
# Bubbled up from the adapter require. Prefix the exception message
# with some guidance about how to address it and reraise.
else
raise LoadError, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
end
end
adapter_method = "#{spec[:adapter]}_connection"
unless ActiveRecord::Base.respond_to?(adapter_method)
raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter"
end
ConnectionSpecification.new(spec.delete(:name) || "primary", db_config, adapter_method)
end
# Returns fully resolved connection, accepts hash, string or symbol.
# Always returns a DatabaseConfiguration::DatabaseConfig
#
# == Examples
#
# Symbol representing current environment.
#
# Resolver.new("production" => {}).resolve(:production)
# # => DatabaseConfigurations::HashConfig.new(env_name: "production", config: {})
#
# One layer deep hash of connection values.
#
# Resolver.new({}).resolve("adapter" => "sqlite3")
# # => DatabaseConfigurations::HashConfig.new(config: {"adapter" => "sqlite3"})
#
# Connection URL.
#
# Resolver.new({}).resolve("postgresql://localhost/foo")
# # => DatabaseConfigurations::UrlConfig.new(config: {"adapter" => "postgresql", "host" => "localhost", "database" => "foo"})
#
def resolve(config_or_env, pool_name = nil)
env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
case config_or_env
when Symbol
resolve_symbol_connection(config_or_env, pool_name)
when String
DatabaseConfigurations::UrlConfig.new(env, "primary", config_or_env)
when Hash
DatabaseConfigurations::HashConfig.new(env, "primary", config_or_env)
when DatabaseConfigurations::DatabaseConfig
config_or_env
else
raise TypeError, "Invalid type for configuration. Expected Symbol, String, or Hash. Got #{config_or_env.inspect}"
end
end
private
# Takes the environment such as +:production+ or +:development+ and a
# pool name the corresponds to the name given by the connection pool
# to the connection. That pool name is merged into the hash with the
# name key.
#
# This requires that the @configurations was initialized with a key that
# matches.
#
# configurations = #<ActiveRecord::DatabaseConfigurations:0x00007fd9fdace3e0
# @configurations=[
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd9fdace250
# @env_name="production", @spec_name="primary", @config={database: "my_db"}>
# ]>
#
# Resolver.new(configurations).resolve_symbol_connection(:production, "primary")
# # => DatabaseConfigurations::HashConfig(config: database: "my_db", env_name: "production", spec_name: "primary")
def resolve_symbol_connection(env_name, pool_name)
db_config = configurations.find_db_config(env_name)
if db_config
config = db_config.configuration_hash.merge(name: pool_name.to_s)
DatabaseConfigurations::HashConfig.new(db_config.env_name, db_config.spec_name, config)
else
raise AdapterNotSpecified, <<~MSG
The `#{env_name}` database is not configured for the `#{ActiveRecord::ConnectionHandling::DEFAULT_ENV.call}` environment.
Available databases configurations are:
#{build_configuration_sentence}
MSG
end
end
def build_configuration_sentence # :nodoc:
configs = configurations.configs_for(include_replicas: true)
configs.group_by(&:env_name).map do |env, config|
namespaces = config.map(&:spec_name)
if namespaces.size > 1
"#{env}: #{namespaces.join(", ")}"
else
env
end
end.join("\n")
end
def initialize(name, db_config)
@name, @db_config = name, db_config
end
end
end
......
# frozen_string_literal: true
module ActiveRecord
module ConnectionAdapters
# Builds a ConnectionSpecification from user input.
class Resolver # :nodoc:
attr_reader :configurations
# Accepts a list of db config objects.
def initialize(configurations)
@configurations = configurations
end
# Returns an instance of ConnectionSpecification for a given adapter.
# Accepts a hash one layer deep that contains all connection information.
#
# == Example
#
# config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
# spec = Resolver.new(config).spec(:production)
# spec.db_config.configuration_hash
# # => { host: "localhost", database: "foo", adapter: "sqlite3" }
#
def spec(config)
pool_name = config if config.is_a?(Symbol)
db_config = resolve(config, pool_name)
spec = db_config.configuration_hash
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
# Require the adapter itself and give useful feedback about
# 1. Missing adapter gems and
# 2. Adapter gems' missing dependencies.
path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter"
begin
require path_to_adapter
rescue LoadError => e
# We couldn't require the adapter itself. Raise an exception that
# points out config typos and missing gems.
if e.path == path_to_adapter
# We can assume that a non-builtin adapter was specified, so it's
# either misspelled or missing from Gemfile.
raise LoadError, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
# Bubbled up from the adapter require. Prefix the exception message
# with some guidance about how to address it and reraise.
else
raise LoadError, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
end
end
unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter"
end
ConnectionSpecification.new(spec.delete(:name) || "primary", db_config)
end
# Returns fully resolved connection, accepts hash, string or symbol.
# Always returns a DatabaseConfiguration::DatabaseConfig
#
# == Examples
#
# Symbol representing current environment.
#
# Resolver.new("production" => {}).resolve(:production)
# # => DatabaseConfigurations::HashConfig.new(env_name: "production", config: {})
#
# One layer deep hash of connection values.
#
# Resolver.new({}).resolve("adapter" => "sqlite3")
# # => DatabaseConfigurations::HashConfig.new(config: {"adapter" => "sqlite3"})
#
# Connection URL.
#
# Resolver.new({}).resolve("postgresql://localhost/foo")
# # => DatabaseConfigurations::UrlConfig.new(config: {"adapter" => "postgresql", "host" => "localhost", "database" => "foo"})
#
def resolve(config_or_env, pool_name = nil)
env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
case config_or_env
when Symbol
resolve_symbol_connection(config_or_env, pool_name)
when String
DatabaseConfigurations::UrlConfig.new(env, "primary", config_or_env)
when Hash
DatabaseConfigurations::HashConfig.new(env, "primary", config_or_env)
when DatabaseConfigurations::DatabaseConfig
config_or_env
else
raise TypeError, "Invalid type for configuration. Expected Symbol, String, or Hash. Got #{config_or_env.inspect}"
end
end
private
# Takes the environment such as +:production+ or +:development+ and a
# pool name the corresponds to the name given by the connection pool
# to the connection. That pool name is merged into the hash with the
# name key.
#
# This requires that the @configurations was initialized with a key that
# matches.
#
# configurations = #<ActiveRecord::DatabaseConfigurations:0x00007fd9fdace3e0
# @configurations=[
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd9fdace250
# @env_name="production", @spec_name="primary", @config={database: "my_db"}>
# ]>
#
# Resolver.new(configurations).resolve_symbol_connection(:production, "primary")
# # => DatabaseConfigurations::HashConfig(config: database: "my_db", env_name: "production", spec_name: "primary")
def resolve_symbol_connection(env_name, pool_name)
db_config = configurations.find_db_config(env_name)
if db_config
config = db_config.configuration_hash.merge(name: pool_name.to_s)
DatabaseConfigurations::HashConfig.new(db_config.env_name, db_config.spec_name, config)
else
raise AdapterNotSpecified, <<~MSG
The `#{env_name}` database is not configured for the `#{ActiveRecord::ConnectionHandling::DEFAULT_ENV.call}` environment.
Available databases configurations are:
#{build_configuration_sentence}
MSG
end
end
def build_configuration_sentence # :nodoc:
configs = configurations.configs_for(include_replicas: true)
configs.group_by(&:env_name).map do |env, config|
namespaces = config.map(&:spec_name)
if namespaces.size > 1
"#{env}: #{namespaces.join(", ")}"
else
env
end
end.join("\n")
end
end
end
end
......@@ -183,7 +183,7 @@ def resolve_config_for_connection(config_or_env) # :nodoc:
pool_name = primary_class? ? "primary" : name
self.connection_specification_name = pool_name
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
resolver = ConnectionAdapters::Resolver.new(Base.configurations)
config_hash = resolver.resolve(config_or_env, pool_name).configuration_hash
config_hash[:name] = pool_name
......@@ -227,7 +227,7 @@ def primary_class? # :nodoc:
#
# Please use only for reading.
def connection_config
connection_pool.spec.underlying_configuration_hash
connection_pool.spec.db_config.configuration_hash
end
def connection_pool
......
......@@ -3,6 +3,7 @@
require "active_record/database_configurations/database_config"
require "active_record/database_configurations/hash_config"
require "active_record/database_configurations/url_config"
require "active_record/database_configurations/connection_url_resolver"
module ActiveRecord
# ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
......
# frozen_string_literal: true
module ActiveRecord
class DatabaseConfigurations
# Expands a connection string into a hash.
class ConnectionUrlResolver # :nodoc:
# == Example
#
# url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
# ConnectionUrlResolver.new(url).to_hash
# # => {
# adapter: "postgresql",
# host: "localhost",
# port: 9000,
# database: "foo_test",
# username: "foo",
# password: "bar",
# pool: "5",
# timeout: "3000"
# }
def initialize(url)
raise "Database URL cannot be empty" if url.blank?
@uri = uri_parser.parse(url)
@adapter = @uri.scheme && @uri.scheme.tr("-", "_")
@adapter = "postgresql" if @adapter == "postgres"
if @uri.opaque
@uri.opaque, @query = @uri.opaque.split("?", 2)
else
@query = @uri.query
end
end
# Converts the given URL to a full connection hash.
def to_hash
config = raw_config.compact_blank
config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String }
config
end
private
attr_reader :uri
def uri_parser
@uri_parser ||= URI::Parser.new
end
# Converts the query parameters of the URI into a hash.
#
# "localhost?pool=5&reaping_frequency=2"
# # => { pool: "5", reaping_frequency: "2" }
#
# returns empty hash if no query present.
#
# "localhost"
# # => {}
def query_hash
Hash[(@query || "").split("&").map { |pair| pair.split("=") }].symbolize_keys
end
def raw_config
if uri.opaque
query_hash.merge(
adapter: @adapter,
database: uri.opaque
)
else
query_hash.merge(
adapter: @adapter,
username: uri.user,
password: uri.password,
port: uri.port,
database: database_from_path,
host: uri.hostname
)
end
end
# Returns name of the database.
def database_from_path
if @adapter == "sqlite3"
# 'sqlite3:/foo' is absolute, because that makes sense. The
# corresponding relative version, 'sqlite3:foo', is handled
# elsewhere, as an "opaque".
uri.path
else
# Only SQLite uses a filename as the "database" name; for
# anything else, a leading slash would be silly.
uri.path.sub(%r{^/}, "")
end
end
end
end
end
......@@ -18,8 +18,12 @@ def config
configuration_hash.stringify_keys
end
def initialize_dup(original)
@config = original.configuration_hash.dup
def adapter_method
"#{adapter}_connection"
end
def adapter
configuration_hash[:adapter]
end
def replica?
......
......@@ -53,7 +53,7 @@ def migrations_paths
private
def resolve_url_key
if configuration_hash[:url] && !configuration_hash[:url].match?(/^jdbc:/)
connection_hash = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(configuration_hash[:url]).to_hash
connection_hash = ConnectionUrlResolver.new(configuration_hash[:url]).to_hash
configuration_hash.merge!(connection_hash)
end
end
......
......@@ -64,7 +64,7 @@ def build_url_hash(url)
if url.nil? || /^jdbc:/.match?(url)
{ url: url }
else
ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash
ConnectionUrlResolver.new(url).to_hash
end
end
......
......@@ -116,7 +116,7 @@ def current_config(options = {})
if options.has_key?(:config)
@current_config = options[:config]
else
@current_config ||= ActiveRecord::Base.configurations.configs_for(env_name: options[:env], spec_name: options[:spec]).underlying_configuration_hash
@current_config ||= ActiveRecord::Base.configurations.configs_for(env_name: options[:env], spec_name: options[:spec]).db_config.configuration_hash
end
end
......@@ -136,7 +136,7 @@ def create_all
old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
each_local_configuration { |configuration| create configuration }
if old_pool
ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec.to_hash)
ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec.db_config.configuration_hash)
end
end
......
......@@ -11,7 +11,7 @@ class Mysql2SchemaTest < ActiveRecord::Mysql2TestCase
def setup
@connection = ActiveRecord::Base.connection
db = Post.connection_pool.spec.underlying_configuration_hash[:database]
db = Post.connection_pool.spec.db_config.configuration_hash[:database]
table = Post.table_name
@db_name = db
......
......@@ -40,7 +40,7 @@ def test_expire_mutates_in_use
def test_close
db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", {})
pool = Pool.new(ConnectionSpecification.new("primary", db_config, nil))
pool = Pool.new(ConnectionSpecification.new("primary", db_config))
pool.insert_connection_for_test! @adapter
@adapter.pool = pool
......
......@@ -27,13 +27,14 @@ def test_default_env_fall_back_to_default_env_when_rails_env_or_rack_env_is_empt
ENV["RACK_ENV"] = original_rack_env
end
def test_establish_connection_uses_spec_name
def test_establish_connection_uses_config_hash_with_spec_name
old_config = ActiveRecord::Base.configurations
config = { "readonly" => { "adapter" => "sqlite3", "pool" => "5" } }
ActiveRecord::Base.configurations = config
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(ActiveRecord::Base.configurations)
spec = resolver.spec(:readonly)
@handler.establish_connection(spec.to_hash)
resolver = ConnectionAdapters::Resolver.new(ActiveRecord::Base.configurations)
config_hash = resolver.resolve(config["readonly"], "readonly").configuration_hash
config_hash[:name] = "readonly"
@handler.establish_connection(config_hash)
assert_not_nil @handler.retrieve_connection_pool("readonly")
ensure
......@@ -62,13 +63,13 @@ def test_establish_connection_using_3_levels_config
@handler.establish_connection(:readonly)
assert_not_nil pool = @handler.retrieve_connection_pool("readonly")
assert_equal "db/readonly.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/readonly.sqlite3", pool.spec.db_config.configuration_hash[:database]
assert_not_nil pool = @handler.retrieve_connection_pool("primary")
assert_equal "db/primary.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/primary.sqlite3", pool.spec.db_config.configuration_hash[:database]
assert_not_nil pool = @handler.retrieve_connection_pool("common")
assert_equal "db/common.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/common.sqlite3", pool.spec.db_config.configuration_hash[:database]
ensure
ActiveRecord::Base.configurations = @prev_configs
ENV["RAILS_ENV"] = previous_env
......@@ -92,7 +93,7 @@ def test_establish_connection_using_3_level_config_defaults_to_default_env_prima
ActiveRecord::Base.establish_connection
assert_match "db/primary.sqlite3", ActiveRecord::Base.connection.pool.spec.underlying_configuration_hash[:database]
assert_match "db/primary.sqlite3", ActiveRecord::Base.connection.pool.spec.db_config.configuration_hash[:database]
ensure
ActiveRecord::Base.configurations = @prev_configs
ENV["RAILS_ENV"] = previous_env
......@@ -115,7 +116,7 @@ def test_establish_connection_using_2_level_config_defaults_to_default_env_prima
ActiveRecord::Base.establish_connection
assert_match "db/primary.sqlite3", ActiveRecord::Base.connection.pool.spec.underlying_configuration_hash[:database]
assert_match "db/primary.sqlite3", ActiveRecord::Base.connection.pool.spec.db_config.configuration_hash[:database]
ensure
ActiveRecord::Base.configurations = @prev_configs
ENV["RAILS_ENV"] = previous_env
......@@ -131,7 +132,7 @@ def test_establish_connection_using_two_level_configurations
@handler.establish_connection(:development)
assert_not_nil pool = @handler.retrieve_connection_pool("development")
assert_equal "db/primary.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/primary.sqlite3", pool.spec.db_config.configuration_hash[:database]
ensure
ActiveRecord::Base.configurations = @prev_configs
end
......@@ -146,7 +147,7 @@ def test_establish_connection_using_top_level_key_in_two_level_config
@handler.establish_connection(:development_readonly)
assert_not_nil pool = @handler.retrieve_connection_pool("development_readonly")
assert_equal "db/readonly.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/readonly.sqlite3", pool.spec.db_config.configuration_hash[:database]
ensure
ActiveRecord::Base.configurations = @prev_configs
end
......@@ -210,7 +211,7 @@ def test_a_class_using_custom_pool_and_switching_back_to_primary
assert_same klass2.connection, ActiveRecord::Base.connection
pool = klass2.establish_connection(ActiveRecord::Base.connection_pool.spec.underlying_configuration_hash)
pool = klass2.establish_connection(ActiveRecord::Base.connection_pool.spec.db_config.configuration_hash)
assert_same klass2.connection, pool.connection
assert_not_same klass2.connection, ActiveRecord::Base.connection
......
......@@ -82,10 +82,10 @@ def test_establish_connection_using_3_levels_config
ActiveRecord::Base.connects_to(database: { writing: :primary, reading: :readonly })
assert_not_nil pool = ActiveRecord::Base.connection_handlers[:writing].retrieve_connection_pool("primary")
assert_equal "db/primary.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/primary.sqlite3", pool.spec.db_config.configuration_hash[:database]
assert_not_nil pool = ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("primary")
assert_equal "db/readonly.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/readonly.sqlite3", pool.spec.db_config.configuration_hash[:database]
ensure
ActiveRecord::Base.configurations = @prev_configs
ActiveRecord::Base.establish_connection(:arunit)
......@@ -140,10 +140,10 @@ def test_establish_connection_using_3_levels_config_with_non_default_handlers
ActiveRecord::Base.connects_to(database: { default: :primary, readonly: :readonly })
assert_not_nil pool = ActiveRecord::Base.connection_handlers[:default].retrieve_connection_pool("primary")
assert_equal "db/primary.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/primary.sqlite3", pool.spec.db_config.configuration_hash[:database]
assert_not_nil pool = ActiveRecord::Base.connection_handlers[:readonly].retrieve_connection_pool("primary")
assert_equal "db/readonly.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/readonly.sqlite3", pool.spec.db_config.configuration_hash[:database]
ensure
ActiveRecord::Base.configurations = @prev_configs
ActiveRecord::Base.establish_connection(:arunit)
......@@ -162,7 +162,7 @@ def test_switching_connections_with_database_url
assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
assert_not_nil pool = handler.retrieve_connection_pool("primary")
assert_equal({ adapter: "postgresql", database: "bar", host: "localhost" }, pool.spec.underlying_configuration_hash)
assert_equal({ adapter: "postgresql", database: "bar", host: "localhost" }, pool.spec.db_config.configuration_hash)
end
ensure
ActiveRecord::Base.establish_connection(:arunit)
......@@ -182,7 +182,7 @@ def test_switching_connections_with_database_config_hash
assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
assert_not_nil pool = handler.retrieve_connection_pool("primary")
assert_equal(config, pool.spec.underlying_configuration_hash)
assert_equal(config, pool.spec.db_config.configuration_hash)
end
ensure
ActiveRecord::Base.establish_connection(:arunit)
......@@ -222,7 +222,7 @@ def test_switching_connections_with_database_symbol_uses_default_role
assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
assert_not_nil pool = handler.retrieve_connection_pool("primary")
assert_equal(config["default_env"]["animals"], pool.spec.underlying_configuration_hash)
assert_equal(config["default_env"]["animals"], pool.spec.db_config.configuration_hash)
end
ensure
ActiveRecord::Base.configurations = @prev_configs
......@@ -249,7 +249,7 @@ def test_switching_connections_with_database_hash_uses_passed_role_and_database
assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
assert_not_nil pool = handler.retrieve_connection_pool("primary")
assert_equal(config["default_env"]["primary"], pool.spec.underlying_configuration_hash)
assert_equal(config["default_env"]["primary"], pool.spec.db_config.configuration_hash)
end
ensure
ActiveRecord::Base.configurations = @prev_configs
......@@ -284,7 +284,7 @@ def test_connects_to_using_top_level_key_in_two_level_config
ActiveRecord::Base.connects_to database: { writing: :development, reading: :development_readonly }
assert_not_nil pool = ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("primary")
assert_equal "db/readonly.sqlite3", pool.spec.underlying_configuration_hash[:database]
assert_equal "db/readonly.sqlite3", pool.spec.db_config.configuration_hash[:database]
ensure
ActiveRecord::Base.configurations = @prev_configs
ActiveRecord::Base.establish_connection(:arunit)
......
# frozen_string_literal: true
require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class ConnectionSpecificationTest < ActiveRecord::TestCase
def test_dup_deep_copy_config
db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new("development", "primary", { a: :b })
spec = ConnectionSpecification.new("primary", db_config, "bar")
assert_not_equal(spec.underlying_configuration_hash.object_id, spec.dup.underlying_configuration_hash.object_id)
end
end
end
end
......@@ -24,7 +24,7 @@ def resolve_config(config)
def resolve_spec(spec, config)
configs = ActiveRecord::DatabaseConfigurations.new(config)
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
resolver = ConnectionAdapters::Resolver.new(configs)
resolver.resolve(spec, spec).configuration_hash
end
......
......@@ -199,7 +199,7 @@ def test_reap_inactive
def test_idle_timeout_configuration
@pool.disconnect!
spec = ActiveRecord::Base.connection_pool.spec
spec.underlying_configuration_hash.merge!(idle_timeout: "0.02")
spec.db_config.configuration_hash.merge!(idle_timeout: "0.02")
@pool = ConnectionPool.new(spec)
idle_conn = @pool.checkout
@pool.checkin(idle_conn)
......@@ -224,7 +224,7 @@ def test_idle_timeout_configuration
def test_disable_flush
@pool.disconnect!
spec = ActiveRecord::Base.connection_pool.spec
spec.underlying_configuration_hash.merge!(idle_timeout: -5)
spec.db_config.configuration_hash.merge!(idle_timeout: -5)
@pool = ConnectionPool.new(spec)
idle_conn = @pool.checkout
@pool.checkin(idle_conn)
......@@ -718,8 +718,12 @@ def test_public_connections_access_threadsafe
private
def with_single_connection_pool
one_conn_spec = ActiveRecord::Base.connection_pool.spec.dup
one_conn_spec.underlying_configuration_hash[:pool] = 1 # this is safe to do, because .dupped ConnectionSpecification also auto-dups its config
old_config = ActiveRecord::Base.connection_pool.spec.db_config.configuration_hash
db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new("arunit", "primary", old_config.dup)
one_conn_spec = ConnectionSpecification.new("primary", db_config)
one_conn_spec.db_config.configuration_hash[:pool] = 1 # this is safe to do, because .dupped ConnectionSpecification also auto-dups its config
yield(pool = ConnectionPool.new(one_conn_spec))
ensure
pool.disconnect! if pool
......
......@@ -8,13 +8,13 @@ class ConnectionSpecification
class ResolverTest < ActiveRecord::TestCase
def resolve(spec, config = {})
configs = ActiveRecord::DatabaseConfigurations.new(config)
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
resolver = ConnectionAdapters::Resolver.new(configs)
resolver.resolve(spec, spec).configuration_hash
end
def spec(spec, config = {})
configs = ActiveRecord::DatabaseConfigurations.new(config)
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(configs)
resolver = ConnectionAdapters::Resolver.new(configs)
resolver.spec(spec)
end
......
......@@ -37,7 +37,7 @@ def current_adapter?(*types)
def in_memory_db?
current_adapter?(:SQLite3Adapter) &&
ActiveRecord::Base.connection_pool.spec.underlying_configuration_hash[:database] == ":memory:"
ActiveRecord::Base.connection_pool.spec.db_config.configuration_hash[:database] == ":memory:"
end
def subsecond_precision_supported?
......
......@@ -58,7 +58,8 @@ def test_some_time
end
def test_pool_has_reaper
spec = ActiveRecord::Base.connection_pool.spec.dup
config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", spec_name: "primary")
spec = ConnectionSpecification.new("primary", config)
pool = ConnectionPool.new spec
assert pool.reaper
......@@ -67,17 +68,19 @@ def test_pool_has_reaper
end
def test_reaping_frequency_configuration
spec = ActiveRecord::Base.connection_pool.spec.dup
spec.underlying_configuration_hash[:reaping_frequency] = "10.01"
spec = duplicated_spec
spec.db_config.configuration_hash[:reaping_frequency] = "10.01"
pool = ConnectionPool.new spec
assert_equal 10.01, pool.reaper.frequency
ensure
pool.discard!
end
def test_connection_pool_starts_reaper
spec = ActiveRecord::Base.connection_pool.spec.dup
spec.underlying_configuration_hash[:reaping_frequency] = "0.0001"
spec = duplicated_spec
spec.db_config.configuration_hash[:reaping_frequency] = "0.0001"
pool = ConnectionPool.new spec
......@@ -94,8 +97,8 @@ def test_connection_pool_starts_reaper
end
def test_reaper_works_after_pool_discard
spec = ActiveRecord::Base.connection_pool.spec.dup
spec.underlying_configuration_hash[:reaping_frequency] = "0.0001"
spec = duplicated_spec
spec.db_config.configuration_hash[:reaping_frequency] = "0.0001"
2.times do
pool = ConnectionPool.new spec
......@@ -116,7 +119,7 @@ def test_reaper_works_after_pool_discard
# This doesn't test the reaper directly, but we want to test the action
# it would take on a discarded pool
def test_reap_flush_on_discarded_pool
spec = ActiveRecord::Base.connection_pool.spec.dup
spec = duplicated_spec
pool = ConnectionPool.new spec
pool.discard!
......@@ -125,8 +128,8 @@ def test_reap_flush_on_discarded_pool
end
def test_connection_pool_starts_reaper_in_fork
spec = ActiveRecord::Base.connection_pool.spec.dup
spec.underlying_configuration_hash[:reaping_frequency] = "0.0001"
spec = duplicated_spec
spec.db_config.configuration_hash[:reaping_frequency] = "0.0001"
pool = ConnectionPool.new spec
pool.checkout
......@@ -169,26 +172,33 @@ def test_reaper_does_not_reap_discarded_connection_pools
pool.discard!
end
def new_conn_in_thread(pool)
event = Concurrent::Event.new
conn = nil
child = Thread.new do
conn = pool.checkout
event.set
Thread.stop
private
def duplicated_spec
old_config = ActiveRecord::Base.connection_pool.spec.db_config.configuration_hash
db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new("arunit", "primary", old_config.dup)
ConnectionSpecification.new("primary", db_config)
end
event.wait
[conn, child]
end
def new_conn_in_thread(pool)
event = Concurrent::Event.new
conn = nil
def wait_for_conn_idle(conn, timeout = 5)
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
while conn.in_use? && Process.clock_gettime(Process::CLOCK_MONOTONIC) - start < timeout
Thread.pass
child = Thread.new do
conn = pool.checkout
event.set
Thread.stop
end
event.wait
[conn, child]
end
def wait_for_conn_idle(conn, timeout = 5)
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
while conn.in_use? && Process.clock_gettime(Process::CLOCK_MONOTONIC) - start < timeout
Thread.pass
end
end
end
end
end
end
......@@ -359,7 +359,7 @@ class TwoMigration < ActiveRecord::Migration::Current
db_migrate_and_schema_dump_and_load "schema"
app_file "db/seeds.rb", <<-RUBY
print Book.connection.pool.spec.underlying_configuration_hash[:database]
print Book.connection.pool.spec.db_config.configuration_hash[:database]
RUBY
output = rails("db:seed")
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册