diff --git a/activerecord/lib/active_record/connection_adapters.rb b/activerecord/lib/active_record/connection_adapters.rb index a78980ad722..01c3484ca73 100644 --- a/activerecord/lib/active_record/connection_adapters.rb +++ b/activerecord/lib/active_record/connection_adapters.rb @@ -10,6 +10,7 @@ module ActiveRecord autoload :Column autoload :ConnectionSpecification + autoload :Resolver autoload_at "active_record/connection_adapters/abstract/schema_definitions" do autoload :IndexDefinition diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 5a8accc18e3..26a3cfd2885 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -385,14 +385,14 @@ module ActiveRecord @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 @@ module ActiveRecord # +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 @@ module ActiveRecord # Raises: # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all # connections in the pool within a timeout interval (default duration is - # spec.underlying_configuration_hash[:checkout_timeout] * 2 seconds). + # spec.db_config.configuration_hash[:checkout_timeout] * 2 seconds). def disconnect(raise_on_acquisition_timeout = true) with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do synchronize do @@ -526,7 +526,7 @@ module ActiveRecord # # The pool first tries to gain ownership of all connections. If unable to # do so within a timeout interval (default duration is - # spec.underlying_configuration_hash[:checkout_timeout] * 2 seconds), then the pool is forcefully + # spec.db_config.configuration_hash[:checkout_timeout] * 2 seconds), then the pool is forcefully # disconnected without any regard for other connection owning threads. def disconnect! disconnect(false) @@ -557,7 +557,7 @@ module ActiveRecord # Raises: # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all # connections in the pool within a timeout interval (default duration is - # spec.underlying_configuration_hash[:checkout_timeout] * 2 seconds). + # spec.db_config.configuration_hash[:checkout_timeout] * 2 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 @@ module ActiveRecord # # The pool first tries to gain ownership of all connections. If unable to # do so within a timeout interval (default duration is - # spec.underlying_configuration_hash[:checkout_timeout] * 2 seconds), then the pool forcefully + # spec.db_config.configuration_hash[:checkout_timeout] * 2 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord } 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 @@ module ActiveRecord 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 @@ module ActiveRecord # 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 diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 77adc163f42..42deb1ccda9 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -4,259 +4,11 @@ require "uri" 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 = # - # ]> - # - # 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 diff --git a/activerecord/lib/active_record/connection_adapters/resolver.rb b/activerecord/lib/active_record/connection_adapters/resolver.rb new file mode 100644 index 00000000000..49061bd579b --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/resolver.rb @@ -0,0 +1,145 @@ +# 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 = # + # ]> + # + # 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 diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 68732d01449..a2ebaa6ffd8 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -183,7 +183,7 @@ module ActiveRecord 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 @@ module ActiveRecord # # 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 diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb index 73d607534b6..d84d57bb057 100644 --- a/activerecord/lib/active_record/database_configurations.rb +++ b/activerecord/lib/active_record/database_configurations.rb @@ -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 diff --git a/activerecord/lib/active_record/database_configurations/connection_url_resolver.rb b/activerecord/lib/active_record/database_configurations/connection_url_resolver.rb new file mode 100644 index 00000000000..bf56b03bb2e --- /dev/null +++ b/activerecord/lib/active_record/database_configurations/connection_url_resolver.rb @@ -0,0 +1,96 @@ +# 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 diff --git a/activerecord/lib/active_record/database_configurations/database_config.rb b/activerecord/lib/active_record/database_configurations/database_config.rb index cb19e9bc088..797da7fda77 100644 --- a/activerecord/lib/active_record/database_configurations/database_config.rb +++ b/activerecord/lib/active_record/database_configurations/database_config.rb @@ -18,8 +18,12 @@ module ActiveRecord 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? diff --git a/activerecord/lib/active_record/database_configurations/hash_config.rb b/activerecord/lib/active_record/database_configurations/hash_config.rb index 9ba849717f8..30a0e0cf867 100644 --- a/activerecord/lib/active_record/database_configurations/hash_config.rb +++ b/activerecord/lib/active_record/database_configurations/hash_config.rb @@ -53,7 +53,7 @@ module ActiveRecord 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 diff --git a/activerecord/lib/active_record/database_configurations/url_config.rb b/activerecord/lib/active_record/database_configurations/url_config.rb index 27d8b1b1ad8..8f45abca708 100644 --- a/activerecord/lib/active_record/database_configurations/url_config.rb +++ b/activerecord/lib/active_record/database_configurations/url_config.rb @@ -64,7 +64,7 @@ module ActiveRecord if url.nil? || /^jdbc:/.match?(url) { url: url } else - ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash + ConnectionUrlResolver.new(url).to_hash end end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 1dca822741a..cc8b57b7ceb 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -116,7 +116,7 @@ module ActiveRecord 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 @@ module ActiveRecord 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 diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index b492560d078..53a2835ae1b 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -11,7 +11,7 @@ module ActiveRecord 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 diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb index 08a2729c419..6f112692471 100644 --- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb +++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb @@ -40,7 +40,7 @@ module ActiveRecord 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 diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 186415bede5..3c8f757bf90 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -27,13 +27,14 @@ module ActiveRecord 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 @@ module ActiveRecord @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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord @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 @@ module ActiveRecord @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 @@ module ActiveRecord 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 diff --git a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb index b93bb28fff2..e9acf344503 100644 --- a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb @@ -82,10 +82,10 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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) diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb deleted file mode 100644 index 44b58c1dc24..00000000000 --- a/activerecord/test/cases/connection_adapters/connection_specification_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# 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 diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb index 7db06c5022a..9586fe6c8f0 100644 --- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb +++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb @@ -24,7 +24,7 @@ module ActiveRecord 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 diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 64930db1bf1..b8749e629a8 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -199,7 +199,7 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 8609f1b584e..542f65bc974 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -8,13 +8,13 @@ module ActiveRecord 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 diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index e5ad8fe441a..e1759ee3271 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -37,7 +37,7 @@ end 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? diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb index 833dea2e127..d718a4d26a7 100644 --- a/activerecord/test/cases/reaper_test.rb +++ b/activerecord/test/cases/reaper_test.rb @@ -58,7 +58,8 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord # 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb index 4997333c3c3..307a4d7cd6f 100644 --- a/railties/test/application/rake/multi_dbs_test.rb +++ b/railties/test/application/rake/multi_dbs_test.rb @@ -359,7 +359,7 @@ module ApplicationTests 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")