Refactor lookup of connection adapters

Right now adapters have to expose a rather byzantine API:

  - They must be defined in `active_record/connection_adapters/<name>_adapter`
  - They must define `ConnectionHandling.<name>_adapter_class`
  - They must define `ConnectionHandling.<name>_connection`

All this is not very DRY and a bit annoying. Additionally it makes
it very hard to define aliases (e.g. `mysql` => `trilogy`), or to
substitute a default adapter for a specialized one.

This refactor aims at making all this easier by exposing a simple
`register` method, that third party adapters can call from a Railtie.
This commit is contained in:
Jean Boussier 2023-11-15 10:00:43 +01:00
parent f98bb7ed41
commit 009c7e7411
31 changed files with 351 additions and 292 deletions

View File

@ -4,6 +4,65 @@ module ActiveRecord
module ConnectionAdapters
extend ActiveSupport::Autoload
@adapters = {}
class << self
# Registers a custom database adapter.
#
# Can also be used to define aliases.
#
# == Example
#
# ActiveRecord::ConnectionAdapters.register("megadb", "MegaDB::ActiveRecordAdapter", "mega_db/active_record_adapter")
#
# ActiveRecord::ConnectionAdapters.register("mysql", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter")
#
def register(name, class_name, path = class_name.underscore)
@adapters[name] = [class_name, path]
end
def resolve(adapter_name) # :nodoc:
# Require the adapter itself and give useful feedback about
# 1. Missing adapter gems and
# 2. Adapter gems' missing dependencies.
class_name, path_to_adapter = @adapters[adapter_name]
unless class_name
raise AdapterNotFound, "database configuration specifies nonexistent '#{adapter_name}' adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile."
end
unless Object.const_defined?(class_name)
begin
require path_to_adapter
rescue LoadError => error
# We couldn't require the adapter itself. Raise an exception that
# points out config typos and missing gems.
if error.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, "Error loading the '#{adapter_name}' Active Record adapter. Ensure that the necessary adapter gem is in the Gemfile. #{error.message}", error.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 '#{adapter_name}' Active Record adapter. Missing a gem it depends on? #{error.message}", error.backtrace
end
end
end
begin
Object.const_get(class_name)
rescue NameError => error
raise AdapterNotFound, "Could not load the #{class_name} Active Record adapter (#{error.message})."
end
end
end
register "sqlite3", "ActiveRecord::ConnectionAdapters::SQLite3Adapter", "active_record/connection_adapters/sqlite3_adapter"
register "mysql2", "ActiveRecord::ConnectionAdapters::Mysql2Adapter", "active_record/connection_adapters/mysql2_adapter"
register "trilogy", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter"
register "postgresql", "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter", "active_record/connection_adapters/postgresql_adapter"
eager_autoload do
autoload :AbstractAdapter
end

View File

@ -324,32 +324,6 @@ module ActiveRecord
db_config = Base.configurations.resolve(config)
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.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/#{db_config.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 '#{db_config.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 '#{db_config.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 #{db_config.adapter} adapter"
end
ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
end

View File

@ -682,7 +682,7 @@ module ActiveRecord
alias_method :release, :remove_connection_from_thread_cache
def new_connection
connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
connection = db_config.new_connection
connection.pool = self
connection
rescue ConnectionNotEstablished => ex

View File

@ -7,17 +7,6 @@ gem "mysql2", "~> 0.5"
require "mysql2"
module ActiveRecord
module ConnectionHandling # :nodoc:
def mysql2_adapter_class
ConnectionAdapters::Mysql2Adapter
end
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
mysql2_adapter_class.new(config)
end
end
module ConnectionAdapters
# = Active Record MySQL2 Adapter
class Mysql2Adapter < AbstractMysqlAdapter

View File

@ -20,17 +20,6 @@ require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
module ActiveRecord
module ConnectionHandling # :nodoc:
def postgresql_adapter_class
ConnectionAdapters::PostgreSQLAdapter
end
# Establishes a connection to the database that's used by all Active Record objects
def postgresql_connection(config)
postgresql_adapter_class.new(config)
end
end
module ConnectionAdapters
# = Active Record PostgreSQL Adapter
#

View File

@ -15,16 +15,6 @@ gem "sqlite3", "~> 1.4"
require "sqlite3"
module ActiveRecord
module ConnectionHandling # :nodoc:
def sqlite3_adapter_class
ConnectionAdapters::SQLite3Adapter
end
def sqlite3_connection(config)
sqlite3_adapter_class.new(config)
end
end
module ConnectionAdapters # :nodoc:
# = Active Record SQLite3 Adapter
#

View File

@ -8,23 +8,6 @@ require "trilogy"
require "active_record/connection_adapters/trilogy/database_statements"
module ActiveRecord
module ConnectionHandling # :nodoc:
def trilogy_adapter_class
ConnectionAdapters::TrilogyAdapter
end
# Establishes a connection to the database that's used by all Active Record objects.
def trilogy_connection(config)
configuration = config.dup
# Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
# matched rather than number of rows updated.
configuration[:found_rows] = true
trilogy_adapter_class.new(configuration)
end
end
module ConnectionAdapters
class TrilogyAdapter < AbstractMysqlAdapter
ER_BAD_DB_ERROR = 1049
@ -90,6 +73,16 @@ module ActiveRecord
end
end
def initialize(config, *)
config = config.dup
# Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
# matched rather than number of rows updated.
config[:found_rows] = true
super
end
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
def supports_json?

View File

@ -8,17 +8,24 @@ module ActiveRecord
class DatabaseConfig # :nodoc:
attr_reader :env_name, :name
def self.new(...)
instance = super
instance.adapter_class # Ensure resolution happens early
instance
end
def initialize(env_name, name)
@env_name = env_name
@name = name
@adapter_class = nil
end
def adapter_method
"#{adapter}_connection"
def adapter_class
@adapter_class ||= ActiveRecord::ConnectionAdapters.resolve(adapter)
end
def adapter_class_method
"#{adapter}_adapter_class"
def new_connection
adapter_class.new(configuration_hash)
end
def host

View File

@ -104,7 +104,7 @@ module ActiveRecord
end
def adapter
configuration_hash[:adapter]
configuration_hash[:adapter]&.to_s
end
# The path to the schema cache dump file for a database.

View File

@ -1,52 +0,0 @@
# frozen_string_literal: true
module ActiveRecord
module ConnectionHandling
def fake_connection(config)
ConnectionAdapters::FakeAdapter.new nil, logger
end
end
module ConnectionAdapters
class FakeAdapter < AbstractAdapter
attr_accessor :data_sources, :primary_keys
@columns = Hash.new { |h, k| h[k] = [] }
class << self
attr_reader :columns
end
def initialize(connection, logger)
super
@data_sources = []
@primary_keys = {}
@columns = self.class.columns
end
def primary_key(table)
@primary_keys[table] || "id"
end
def merge_column(table_name, name, sql_type = nil, options = {})
@columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s,
options[:default],
fetch_type_metadata(sql_type),
options[:null],
)
end
def columns(table_name)
@columns[table_name]
end
def data_source_exists?(*)
true
end
def active?
true
end
end
end
end

View File

@ -23,9 +23,9 @@ class ConnectionTest < ActiveRecord::AbstractMysqlTestCase
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
configuration = db_config.configuration_hash.merge(database: "inexistent_activerecord_unittest")
connection = if current_adapter?(:Mysql2Adapter)
ActiveRecord::Base.mysql2_connection(configuration)
ActiveRecord::ConnectionAdapters::Mysql2Adapter.new(configuration)
else
ActiveRecord::Base.trilogy_connection(configuration)
ActiveRecord::ConnectionAdapters::TrilogyAdapter.new(configuration)
end
connection.drop_table "ex", if_exists: true
end

View File

@ -15,7 +15,7 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
def test_connection_error
error = assert_raises ActiveRecord::ConnectionNotEstablished do
ActiveRecord::Base.mysql2_connection(socket: File::NULL, prepared_statements: false).connect!
ActiveRecord::ConnectionAdapters::Mysql2Adapter.new(socket: File::NULL, prepared_statements: false).connect!
end
assert_kind_of ActiveRecord::ConnectionAdapters::NullPool, error.connection_pool
end

View File

@ -20,7 +20,7 @@ module ActiveRecord
def test_connection_error
error = assert_raises ActiveRecord::ConnectionNotEstablished do
ActiveRecord::Base.postgresql_connection(host: File::NULL).connect!
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new(host: File::NULL).connect!
end
assert_kind_of ActiveRecord::ConnectionAdapters::NullPool, error.connection_pool
end
@ -75,7 +75,7 @@ module ActiveRecord
assert_raise ActiveRecord::NoDatabaseError do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
configuration = db_config.configuration_hash.merge(database: "should_not_exist-cinco-dog-db")
connection = ActiveRecord::Base.postgresql_connection(configuration)
connection = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new(configuration)
connection.exec_query("SELECT 1")
end
end
@ -87,7 +87,7 @@ module ActiveRecord
error = assert_raises ActiveRecord::ConnectionNotEstablished do
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
configuration = db_config.configuration_hash.merge(database: "postgres")
connection = ActiveRecord::Base.postgresql_connection(configuration)
connection = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new(configuration)
connection.exec_query("SELECT 1")
end
assert_not_nil connection
@ -640,7 +640,7 @@ module ActiveRecord
def connection_without_insert_returning
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
ActiveRecord::Base.postgresql_connection(db_config.configuration_hash.merge(insert_returning: false))
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new(db_config.configuration_hash.merge(insert_returning: false))
end
end
end

View File

@ -16,14 +16,16 @@ module ActiveRecord
end
def setup
@conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
timeout: 100
@conn = SQLite3Adapter.new(
database: ":memory:",
adapter: "sqlite3",
timeout: 100,
)
end
def test_bad_connection
error = assert_raise ActiveRecord::NoDatabaseError do
connection = ActiveRecord::Base.sqlite3_connection(adapter: "sqlite3", database: "/tmp/should/_not/_exist/-cinco-dog.db")
connection = SQLite3Adapter.new(adapter: "sqlite3", database: "/tmp/should/_not/_exist/-cinco-dog.db")
connection.drop_table "ex", if_exists: true
end
assert_kind_of ActiveRecord::ConnectionAdapters::NullPool, error.connection_pool
@ -117,15 +119,17 @@ module ActiveRecord
def test_connection_no_db
assert_raises(ArgumentError) do
Base.sqlite3_connection({})
SQLite3Adapter.new({})
end
end
def test_bad_timeout
exception = assert_raises(ActiveRecord::StatementInvalid) do
Base.sqlite3_connection(database: ":memory:",
adapter: "sqlite3",
timeout: "usa").connect!
SQLite3Adapter.new(
database: ":memory:",
adapter: "sqlite3",
timeout: "usa",
).connect!
end
assert_match("TypeError", exception.message)
assert_kind_of ActiveRecord::ConnectionAdapters::NullPool, exception.connection_pool
@ -133,9 +137,11 @@ module ActiveRecord
# connection is OK with a nil timeout
def test_nil_timeout
conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
timeout: nil
conn = SQLite3Adapter.new(
database: ":memory:",
adapter: "sqlite3",
timeout: nil,
)
conn.connect!
assert conn, "made a connection"
end
@ -277,9 +283,11 @@ module ActiveRecord
def test_exec_insert_with_returning_disabled
original_conn = @conn
@conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
insert_returning: false
@conn = SQLite3Adapter.new(
database: ":memory:",
adapter: "sqlite3",
insert_returning: false,
)
with_example_table do
result = @conn.exec_insert("insert into ex (number) VALUES ('foo')", nil, [], "id")
expect = @conn.query("select max(id) from ex").first.first
@ -290,9 +298,11 @@ module ActiveRecord
def test_exec_insert_default_values_with_returning_disabled
original_conn = @conn
@conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
insert_returning: false
@conn = SQLite3Adapter.new(
database: ":memory:",
adapter: "sqlite3",
insert_returning: false,
)
with_example_table do
result = @conn.exec_insert("insert into ex DEFAULT VALUES", nil, [], "id")
expect = @conn.query("select max(id) from ex").first.first
@ -689,35 +699,43 @@ module ActiveRecord
end
def test_db_is_not_readonly_when_readonly_option_is_false
conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
readonly: false
conn = SQLite3Adapter.new(
database: ":memory:",
adapter: "sqlite3",
readonly: false,
)
conn.connect!
assert_not_predicate conn.raw_connection, :readonly?
end
def test_db_is_not_readonly_when_readonly_option_is_unspecified
conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3"
conn = SQLite3Adapter.new(
database: ":memory:",
adapter: "sqlite3",
)
conn.connect!
assert_not_predicate conn.raw_connection, :readonly?
end
def test_db_is_readonly_when_readonly_option_is_true
conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
readonly: true
conn = SQLite3Adapter.new(
database: ":memory:",
adapter: "sqlite3",
readonly: true,
)
conn.connect!
assert_predicate conn.raw_connection, :readonly?
end
def test_writes_are_not_permitted_to_readonly_databases
conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
readonly: true
conn = SQLite3Adapter.new(
database: ":memory:",
adapter: "sqlite3",
readonly: true,
)
conn.connect!
exception = assert_raises(ActiveRecord::StatementInvalid) do
@ -728,7 +746,7 @@ module ActiveRecord
end
def test_strict_strings_by_default
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3")
conn = SQLite3Adapter.new(database: ":memory:", adapter: "sqlite3")
conn.create_table :testings
assert_nothing_raised do
@ -736,7 +754,7 @@ module ActiveRecord
end
with_strict_strings_by_default do
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3")
conn = SQLite3Adapter.new(database: ":memory:", adapter: "sqlite3")
conn.create_table :testings
error = assert_raises(StandardError) do
@ -748,7 +766,7 @@ module ActiveRecord
end
def test_strict_strings_by_default_and_true_in_database_yml
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3", strict: true)
conn = SQLite3Adapter.new(database: ":memory:", adapter: "sqlite3", strict: true)
conn.create_table :testings
error = assert_raises(StandardError) do
@ -758,7 +776,7 @@ module ActiveRecord
assert_equal conn.pool, error.connection_pool
with_strict_strings_by_default do
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3", strict: true)
conn = SQLite3Adapter.new(database: ":memory:", adapter: "sqlite3", strict: true)
conn.create_table :testings
error = assert_raises(StandardError) do
@ -770,7 +788,7 @@ module ActiveRecord
end
def test_strict_strings_by_default_and_false_in_database_yml
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3", strict: false)
conn = SQLite3Adapter.new(database: ":memory:", adapter: "sqlite3", strict: false)
conn.create_table :testings
assert_nothing_raised do
@ -778,7 +796,7 @@ module ActiveRecord
end
with_strict_strings_by_default do
conn = Base.sqlite3_connection(database: ":memory:", adapter: "sqlite3", strict: false)
conn = SQLite3Adapter.new(database: ":memory:", adapter: "sqlite3", strict: false)
conn.create_table :testings
assert_nothing_raised do
@ -852,7 +870,7 @@ module ActiveRecord
options = options.dup
db_config = ActiveRecord::Base.configurations.configurations.find { |config| !config.database.include?(":memory:") }
options[:database] ||= db_config.database
conn = ActiveRecord::Base.sqlite3_connection(options)
conn = SQLite3Adapter.new(options)
yield(conn)
ensure

View File

@ -9,9 +9,11 @@ module ActiveRecord
def test_sqlite_creates_directory
Dir.mktmpdir do |dir|
dir = Pathname.new(dir)
@conn = Base.sqlite3_connection database: dir.join("db/foo.sqlite3"),
adapter: "sqlite3",
timeout: 100
@conn = SQLite3Adapter.new(
database: dir.join("db/foo.sqlite3"),
adapter: "sqlite3",
timeout: 100,
)
@conn.connect!
assert Dir.exist? dir.join("db")

View File

@ -117,7 +117,7 @@ class SQLite3TransactionTest < ActiveRecord::SQLite3TestCase
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
options[:database] ||= db_config.database
end
conn = ActiveRecord::Base.sqlite3_connection(options)
conn = ActiveRecord::ConnectionAdapters::SQLite3Adapter.new(options)
yield(conn)
ensure

View File

@ -14,7 +14,7 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase
test "connection_error" do
error = assert_raises ActiveRecord::ConnectionNotEstablished do
ActiveRecord::Base.trilogy_connection(host: "invalid", port: 12345).connect!
ActiveRecord::ConnectionAdapters::TrilogyAdapter.new(host: "invalid", port: 12345).connect!
end
assert_kind_of ActiveRecord::ConnectionAdapters::NullPool, error.connection_pool
end

View File

@ -39,7 +39,7 @@ module ActiveRecord
end
def test_close
db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", {})
db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", adapter: "abstract")
pool_config = ActiveRecord::ConnectionAdapters::PoolConfig.new(ActiveRecord::Base, db_config, :writing, :default)
pool = Pool.new(pool_config)
pool.insert_connection_for_test! @adapter

View File

@ -9,12 +9,14 @@ module ActiveRecord
@previous_database_url = ENV.delete("DATABASE_URL")
@previous_rack_env = ENV.delete("RACK_ENV")
@previous_rails_env = ENV.delete("RAILS_ENV")
@adapters_was = ActiveRecord::ConnectionAdapters.instance_variable_get(:@adapters).dup
end
teardown do
ENV["DATABASE_URL"] = @previous_database_url
ENV["RACK_ENV"] = @previous_rack_env
ENV["RAILS_ENV"] = @previous_rails_env
ActiveRecord::ConnectionAdapters.instance_variable_set(:@adapters, @adapters_was)
end
def resolve_config(config, env_name = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call)
@ -45,7 +47,7 @@ module ActiveRecord
def test_resolver_with_database_uri_and_current_env_symbol_key
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
config = { "not_production" => { "adapter" => "abstract", "database" => "not_foo" } }
actual = resolve_db_config(:default_env, config)
expected = { adapter: "postgresql", database: "foo", host: "localhost" }
@ -56,7 +58,7 @@ module ActiveRecord
ENV["DATABASE_URL"] = "postgres://localhost/foo"
ENV["RAILS_ENV"] = "foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
config = { "not_production" => { "adapter" => "abstract", "database" => "not_foo" } }
actual = resolve_db_config(:foo, config)
expected = { adapter: "postgresql", database: "foo", host: "localhost" }
@ -65,9 +67,9 @@ module ActiveRecord
def test_resolver_with_nil_database_url_and_current_env
ENV["RAILS_ENV"] = "foo"
config = { "foo" => { "adapter" => "postgres", "url" => ENV["DATABASE_URL"] } }
config = { "foo" => { "adapter" => "postgresql", "url" => ENV["DATABASE_URL"] } }
actual = resolve_db_config(:foo, config)
expected_config = { adapter: "postgres" }
expected_config = { adapter: "postgresql" }
assert_equal expected_config, actual.configuration_hash
end
@ -76,7 +78,7 @@ module ActiveRecord
ENV["DATABASE_URL"] = "postgres://localhost/foo"
ENV["RACK_ENV"] = "foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
config = { "not_production" => { "adapter" => "abstract", "database" => "not_foo" } }
actual = resolve_db_config(:foo, config)
expected = { adapter: "postgresql", database: "foo", host: "localhost" }
@ -85,9 +87,9 @@ module ActiveRecord
def test_resolver_with_database_uri_and_known_key
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
config = { "production" => { "adapter" => "abstract", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_db_config(:production, config)
expected = { adapter: "not_postgres", database: "not_foo", host: "localhost" }
expected = { adapter: "abstract", database: "not_foo", host: "localhost" }
assert_equal expected, actual.configuration_hash
end
@ -105,15 +107,15 @@ module ActiveRecord
def test_resolver_with_database_uri_and_unknown_symbol_key
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
config = { "not_production" => { "adapter" => "abstract", "database" => "not_foo" } }
assert_raises AdapterNotSpecified do
resolve_db_config(:production, config)
end
end
def test_resolver_with_database_uri_and_supplied_url
ENV["DATABASE_URL"] = "not-postgres://not-localhost/not_foo"
config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } }
ENV["DATABASE_URL"] = "abstract://not-localhost/not_foo"
config = { "production" => { "adapter" => "abstract", "database" => "also_not_foo" } }
actual = resolve_db_config("postgres://localhost/foo", config)
expected = { adapter: "postgresql", database: "foo", host: "localhost" }
@ -121,26 +123,26 @@ module ActiveRecord
end
def test_jdbc_url
config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } }
config = { "production" => { "adapter" => "abstract", "url" => "jdbc:postgres://localhost/foo" } }
actual = resolve_config(config, "production")
assert_equal config["production"].symbolize_keys, actual
end
def test_http_url
config = { "production" => { "url" => "http://example.com/path" } }
config = { "production" => { "adapter" => "abstract", "url" => "http://example.com/path" } }
actual = resolve_config(config, "production")
assert_equal config["production"].symbolize_keys, actual
end
def test_https_url
config = { "production" => { "url" => "https://example.com" } }
config = { "production" => { "adapter" => "abstract", "url" => "https://example.com" } }
actual = resolve_config(config, "production")
assert_equal config["production"].symbolize_keys, actual
end
def test_environment_does_not_exist_in_config_url_does_exist
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
config = { "not_default_env" => { "adapter" => "abstract", "database" => "not_foo" } }
actual = resolve_config(config, "default_env")
expect_prod = {
adapter: "postgresql",
@ -152,8 +154,9 @@ module ActiveRecord
end
def test_url_with_hyphenated_scheme
ActiveRecord::ConnectionAdapters.register("ibm_db", "ActiveRecord::ConnectionAdapters::AbstractAdapter", "active_record/connection_adapters/abstract_adapter")
ENV["DATABASE_URL"] = "ibm-db://localhost/foo"
config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
config = { "default_env" => { "adapter" => "abstract", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_db_config(:default_env, config)
expected = { adapter: "ibm_db", database: "foo", host: "localhost" }
@ -199,7 +202,7 @@ module ActiveRecord
end
def test_hash
config = { "production" => { "adapter" => "postgres", "database" => "foo" } }
config = { "production" => { "adapter" => "postgresql", "database" => "foo" } }
actual = resolve_config(config, "production")
assert_equal config["production"].symbolize_keys, actual
end
@ -270,7 +273,7 @@ module ActiveRecord
end
def test_url_sub_key_with_database_url
ENV["DATABASE_URL"] = "NOT-POSTGRES://localhost/NOT_FOO"
ENV["DATABASE_URL"] = "abstract://localhost/NOT_FOO"
config = { "default_env" => { "url" => "postgres://localhost/foo" } }
actual = resolve_config(config)
@ -286,7 +289,7 @@ module ActiveRecord
def test_no_url_sub_key_with_database_url_doesnt_trample_other_envs
ENV["DATABASE_URL"] = "postgres://localhost/baz"
config = { "default_env" => { "database" => "foo" }, "other_env" => { "url" => "postgres://foohost/bardb" } }
config = { "default_env" => { "adapter" => "abstract", "database" => "foo" }, "other_env" => { "url" => "postgres://foohost/bardb" } }
expected = {
default_env: {
database: "baz",
@ -307,7 +310,7 @@ module ActiveRecord
def test_merge_no_conflicts_with_database_url
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "default_env" => { "pool" => "5" } }
config = { "default_env" => { "adapter" => "abstract", "pool" => "5" } }
actual = resolve_config(config)
expected = {
adapter: "postgresql",
@ -322,7 +325,7 @@ module ActiveRecord
def test_merge_conflicts_with_database_url
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
config = { "default_env" => { "adapter" => "abstract", "database" => "NOT-FOO", "pool" => "5" } }
actual = resolve_config(config)
expected = {
adapter: "postgresql",
@ -352,7 +355,7 @@ module ActiveRecord
def test_merge_no_conflicts_with_database_url_and_numeric_pool
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "default_env" => { "pool" => 5 } }
config = { "default_env" => { "adapter" => "abstract", "pool" => 5 } }
actual = resolve_config(config)
expected = {
adapter: "postgresql",
@ -369,8 +372,8 @@ module ActiveRecord
config = {
"default_env" => {
"primary" => { "pool" => 5 },
"animals" => { "pool" => 5 }
"primary" => { "adapter" => "abstract", "pool" => 5 },
"animals" => { "adapter" => "abstract", "pool" => 5 }
}
}
@ -387,7 +390,7 @@ module ActiveRecord
configs = ActiveRecord::DatabaseConfigurations.new(config)
actual = configs.configs_for(env_name: "default_env", name: "animals").configuration_hash
expected = { pool: 5 }
expected = { adapter: "abstract", pool: 5 }
assert_equal expected, actual
end
@ -399,8 +402,8 @@ module ActiveRecord
config = {
"default_env" => {
"primary" => { "pool" => 5 },
"animals" => { "pool" => 5 }
"primary" => { "adapter" => "abstract", "pool" => 5 },
"animals" => { "adapter" => "abstract", "pool" => 5 }
}
}
@ -418,7 +421,7 @@ module ActiveRecord
def test_does_not_change_other_environments
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" }, "default_env" => {} }
config = { "production" => { "adapter" => "abstract", "database" => "not_foo", "host" => "localhost" }, "default_env" => {} }
actual = resolve_db_config(:production, config)
assert_equal config["production"].symbolize_keys, actual.configuration_hash

View File

@ -10,7 +10,7 @@ module ActiveRecord
super
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
@connection = ActiveRecord::Base.public_send(db_config.adapter_method, db_config.configuration_hash)
@connection = db_config.new_connection
end
def test_can_query

View File

@ -6,143 +6,143 @@ module ActiveRecord
class DatabaseConfigurations
class HashConfigTest < ActiveRecord::TestCase
def test_pool_default_when_nil
config = HashConfig.new("default_env", "primary", pool: nil)
config = HashConfig.new("default_env", "primary", pool: nil, adapter: "abstract")
assert_equal 5, config.pool
end
def test_pool_overrides_with_value
config = HashConfig.new("default_env", "primary", pool: "0")
config = HashConfig.new("default_env", "primary", pool: "0", adapter: "abstract")
assert_equal 0, config.pool
end
def test_when_no_pool_uses_default
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal 5, config.pool
end
def test_min_threads_with_value
config = HashConfig.new("default_env", "primary", min_threads: "1")
config = HashConfig.new("default_env", "primary", min_threads: "1", adapter: "abstract")
assert_equal 1, config.min_threads
end
def test_min_threads_default
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal 0, config.min_threads
end
def test_max_threads_with_value
config = HashConfig.new("default_env", "primary", max_threads: "10")
config = HashConfig.new("default_env", "primary", max_threads: "10", adapter: "abstract")
assert_equal 10, config.max_threads
end
def test_max_threads_default_uses_pool_default
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal 5, config.pool
assert_equal 5, config.max_threads
end
def test_max_threads_uses_pool_when_set
config = HashConfig.new("default_env", "primary", pool: 1)
config = HashConfig.new("default_env", "primary", pool: 1, adapter: "abstract")
assert_equal 1, config.pool
assert_equal 1, config.max_threads
end
def test_max_queue_is_pool_multiplied_by_4
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal 5, config.max_threads
assert_equal config.max_threads * 4, config.max_queue
end
def test_checkout_timeout_default_when_nil
config = HashConfig.new("default_env", "primary", checkout_timeout: nil)
config = HashConfig.new("default_env", "primary", checkout_timeout: nil, adapter: "abstract")
assert_equal 5.0, config.checkout_timeout
end
def test_checkout_timeout_overrides_with_value
config = HashConfig.new("default_env", "primary", checkout_timeout: "0")
config = HashConfig.new("default_env", "primary", checkout_timeout: "0", adapter: "abstract")
assert_equal 0.0, config.checkout_timeout
end
def test_when_no_checkout_timeout_uses_default
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal 5.0, config.checkout_timeout
end
def test_reaping_frequency_default_when_nil
config = HashConfig.new("default_env", "primary", reaping_frequency: nil)
config = HashConfig.new("default_env", "primary", reaping_frequency: nil, adapter: "abstract")
assert_nil config.reaping_frequency
end
def test_reaping_frequency_overrides_with_value
config = HashConfig.new("default_env", "primary", reaping_frequency: "0")
config = HashConfig.new("default_env", "primary", reaping_frequency: "0", adapter: "abstract")
assert_equal 0.0, config.reaping_frequency
end
def test_when_no_reaping_frequency_uses_default
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal 60.0, config.reaping_frequency
end
def test_idle_timeout_default_when_nil
config = HashConfig.new("default_env", "primary", idle_timeout: nil)
config = HashConfig.new("default_env", "primary", idle_timeout: nil, adapter: "abstract")
assert_nil config.idle_timeout
end
def test_idle_timeout_overrides_with_value
config = HashConfig.new("default_env", "primary", idle_timeout: "1")
config = HashConfig.new("default_env", "primary", idle_timeout: "1", adapter: "abstract")
assert_equal 1.0, config.idle_timeout
end
def test_when_no_idle_timeout_uses_default
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal 300.0, config.idle_timeout
end
def test_idle_timeout_nil_when_less_than_or_equal_to_zero
config = HashConfig.new("default_env", "primary", idle_timeout: "0")
config = HashConfig.new("default_env", "primary", idle_timeout: "0", adapter: "abstract")
assert_nil config.idle_timeout
end
def test_default_schema_dump_value
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal "schema.rb", config.schema_dump
end
def test_schema_dump_value_set_to_filename
config = HashConfig.new("default_env", "primary", { schema_dump: "my_schema.rb" })
config = HashConfig.new("default_env", "primary", { schema_dump: "my_schema.rb", adapter: "abstract" })
assert_equal "my_schema.rb", config.schema_dump
end
def test_schema_dump_value_set_to_nil
config = HashConfig.new("default_env", "primary", { schema_dump: nil })
config = HashConfig.new("default_env", "primary", { schema_dump: nil, adapter: "abstract" })
assert_nil config.schema_dump
end
def test_schema_dump_value_set_to_false
config = HashConfig.new("default_env", "primary", { schema_dump: false })
config = HashConfig.new("default_env", "primary", { schema_dump: false, adapter: "abstract" })
assert_nil config.schema_dump
end
def test_database_tasks_defaults_to_true
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal true, config.database_tasks?
end
def test_database_tasks_overrides_with_value
config = HashConfig.new("default_env", "primary", database_tasks: false)
config = HashConfig.new("default_env", "primary", database_tasks: false, adapter: "abstract")
assert_equal false, config.database_tasks?
config = HashConfig.new("default_env", "primary", database_tasks: "str")
config = HashConfig.new("default_env", "primary", database_tasks: "str", adapter: "abstract")
assert_equal true, config.database_tasks?
end
def test_schema_cache_path_default_for_primary
config = HashConfig.new("default_env", "primary", {})
config = HashConfig.new("default_env", "primary", adapter: "abstract")
assert_equal "db/schema_cache.yml", config.default_schema_cache_path
end
def test_schema_cache_path_configuration_hash
config = HashConfig.new("default_env", "primary", { schema_cache_path: "db/config_schema_cache.yml" })
config = HashConfig.new("default_env", "primary", { schema_cache_path: "db/config_schema_cache.yml", adapter: "abstract" })
assert_equal "db/config_schema_cache.yml", config.schema_cache_path
end
end

View File

@ -12,19 +12,11 @@ module ActiveRecord
end
def test_url_invalid_adapter
error = assert_raises(LoadError) do
error = assert_raises(AdapterNotFound) do
Base.connection_handler.establish_connection "ridiculous://foo?encoding=utf8"
end
assert_match "Could not load the 'ridiculous' 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.", error.message
end
def test_error_if_no_adapter_method
error = assert_raises(AdapterNotFound) do
Base.connection_handler.establish_connection "abstract://foo?encoding=utf8"
end
assert_match "database configuration specifies nonexistent abstract adapter", error.message
assert_match "database configuration specifies nonexistent 'ridiculous' adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", error.message
end
# The abstract adapter is used simply to bypass the bit of code that

View File

@ -57,12 +57,15 @@ class DatabaseConfigurationsTest < ActiveRecord::TestCase
config = ActiveRecord::DatabaseConfigurations.new({
"test" => {
"config_1" => {
"adapter" => "abstract",
"database" => "db"
},
"config_2" => {
"adapter" => "abstract",
"database" => "db"
},
"config_3" => {
"adapter" => "abstract",
"database" => "db"
},
}
@ -81,7 +84,7 @@ class DatabaseConfigurationsTest < ActiveRecord::TestCase
def test_find_db_config_prioritize_db_config_object_for_the_current_env
config = ActiveRecord::DatabaseConfigurations.new({
"primary" => {
"adapter" => "randomadapter"
"adapter" => "abstract",
},
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call => {
"primary" => {
@ -118,12 +121,14 @@ class DatabaseConfigurationsTest < ActiveRecord::TestCase
configs = ActiveRecord::DatabaseConfigurations.new({
"test" => {
"config_1" => {
"adapter" => "abstract",
"database" => "db",
"custom_config" => {
"sharded" => 1
}
},
"config_2" => {
"adapter" => "abstract",
"database" => "db"
}
}

View File

@ -31,6 +31,9 @@ QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name("type")
ActiveRecord.raise_on_assign_to_attr_readonly = true
ActiveRecord.belongs_to_required_validates_foreign_key = false
ActiveRecord::ConnectionAdapters.register("abstract", "ActiveRecord::ConnectionAdapters::AbstractAdapter", "active_record/connection_adapters/abstract_adapter")
ActiveRecord::ConnectionAdapters.register("fake", "FakeActiveRecordAdapter", File.expand_path("../support/fake_adapter.rb", __dir__))
class SQLSubscriber
attr_reader :logged
attr_reader :payloads

View File

@ -605,7 +605,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
def test_does_not_create_foreign_keys_when_bypassed_by_config
require "active_record/connection_adapters/sqlite3_adapter"
connection = ActiveRecord::Base.sqlite3_connection(
connection = ActiveRecord::ConnectionAdapters::SQLite3Adapter.new(
adapter: "sqlite3",
database: ":memory:",
foreign_keys: false,

View File

@ -221,6 +221,16 @@ module ActiveRecord
end
class DatabaseTasksRegisterTask < ActiveRecord::TestCase
setup do
@tasks_was = ActiveRecord::Tasks::DatabaseTasks.instance_variable_get(:@tasks).dup
@adapters_was = ActiveRecord::ConnectionAdapters.instance_variable_get(:@adapters).dup
end
teardown do
ActiveRecord::Tasks::DatabaseTasks.instance_variable_set(:@tasks, @tasks_was)
ActiveRecord::ConnectionAdapters.instance_variable_set(:@adapters, @adapters_was)
end
def test_register_task
klazz = Class.new do
def initialize(*arguments); end
@ -230,8 +240,8 @@ module ActiveRecord
klazz.stub(:new, instance) do
assert_called_with(instance, :structure_dump, ["awesome-file.sql", nil]) do
ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz)
ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :foo }, "awesome-file.sql")
ActiveRecord::Tasks::DatabaseTasks.register_task(/abstract/, klazz)
ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => "abstract" }, "awesome-file.sql")
end
end
end
@ -245,6 +255,7 @@ module ActiveRecord
klazz.stub(:new, instance) do
assert_called_with(instance, :structure_dump, ["awesome-file.sql", nil]) do
ActiveRecord::ConnectionAdapters.register("custom_mysql", "ActiveRecord::ConnectionAdapters::Mysql2Adapter", "active_record/connection_adapters/mysql2_adapter")
ActiveRecord::Tasks::DatabaseTasks.register_task(/custom_mysql/, klazz)
ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :custom_mysql }, "awesome-file.sql")
end
@ -253,7 +264,7 @@ module ActiveRecord
def test_unregistered_task
assert_raise(ActiveRecord::Tasks::DatabaseNotSupported) do
ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :bar }, "awesome-file.sql")
ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => "abstract" }, "awesome-file.sql")
end
end
end
@ -391,7 +402,7 @@ module ActiveRecord
class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
def setup
@configurations = { "development" => { "database" => "my-db" } }
@configurations = { "development" => { "adapter" => "abstract", "database" => "my-db" } }
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
@ -480,8 +491,8 @@ module ActiveRecord
def setup
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"development" => { "adapter" => "abstract", "database" => "dev-db" },
"test" => { "adapter" => "abstract", "database" => "test-db" },
"production" => { "url" => "abstract://prod-db-host/prod-db" }
}
end
@ -603,9 +614,17 @@ module ActiveRecord
def setup
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
"development" => {
"primary" => { "adapter" => "abstract", "database" => "dev-db" },
"secondary" => { "adapter" => "abstract", "database" => "secondary-dev-db" },
},
"test" => {
"primary" => { "adapter" => "abstract", "database" => "test-db" },
"secondary" => { "adapter" => "abstract", "database" => "secondary-test-db" },
},
"production" => {
"primary" => { "url" => "abstract://prod-db-host/prod-db" },
"secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
}
end
@ -727,7 +746,7 @@ module ActiveRecord
class DatabaseTasksDropAllTest < ActiveRecord::TestCase
def setup
@configurations = { development: { "database" => "my-db" } }
@configurations = { development: { "adapter" => "abstract", "database" => "my-db" } }
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
@ -814,8 +833,8 @@ module ActiveRecord
def setup
@configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"development" => { "adapter" => "abstract", "database" => "dev-db" },
"test" => { "adapter" => "abstract", "database" => "test-db" },
"production" => { "url" => "abstract://prod-db-host/prod-db" }
}
end
@ -905,9 +924,18 @@ module ActiveRecord
def setup
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
"development" => {
"primary" => { "adapter" => "abstract", "database" => "dev-db" },
"secondary" => { "adapter" => "abstract", "database" => "secondary-dev-db" },
},
"test" => {
"primary" => { "adapter" => "abstract", "database" => "test-db" },
"secondary" => { "adapter" => "abstract", "database" => "secondary-test-db" },
},
"production" => {
"primary" => { "url" => "abstract://prod-db-host/prod-db" },
"secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" },
},
}
end
@ -1231,9 +1259,9 @@ module ActiveRecord
def test_purges_current_environment_database
old_configurations = ActiveRecord::Base.configurations
configurations = {
"development" => { "database" => "dev-db" },
"test" => { "database" => "test-db" },
"production" => { "database" => "prod-db" }
"development" => { "adapter" => "abstract", "database" => "dev-db" },
"test" => { "adapter" => "abstract", "database" => "test-db" },
"production" => { "adapter" => "abstract", "database" => "prod-db" },
}
ActiveRecord::Base.configurations = configurations
@ -1255,7 +1283,7 @@ module ActiveRecord
class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase
def test_purge_all_local_configurations
old_configurations = ActiveRecord::Base.configurations
configurations = { development: { "database" => "my-db" } }
configurations = { development: { "adapter" => "abstract", "database" => "my-db" } }
ActiveRecord::Base.configurations = configurations
assert_called_with(
@ -1345,9 +1373,18 @@ module ActiveRecord
def setup
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
"development" => {
"primary" => { "adapter" => "abstract", "database" => "dev-db" },
"secondary" => { "adapter" => "abstract", "database" => "secondary-dev-db" },
},
"test" => {
"primary" => { "adapter" => "abstract", "database" => "test-db" },
"secondary" => { "adapter" => "abstract", "database" => "secondary-test-db" },
},
"production" => {
"primary" => { "url" => "abstract://prod-db-host/prod-db" },
"secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" },
}
}
end
@ -1451,7 +1488,7 @@ module ActiveRecord
def test_charset_current
old_configurations = ActiveRecord::Base.configurations
configurations = {
"production" => { "database" => "prod-db" }
"production" => { "adapter" => "abstract", "database" => "prod-db" }
}
ActiveRecord::Base.configurations = configurations
@ -1484,7 +1521,7 @@ module ActiveRecord
def test_collation_current
old_configurations = ActiveRecord::Base.configurations
configurations = {
"production" => { "database" => "prod-db" }
"production" => { "adapter" => "abstract", "database" => "prod-db" }
}
ActiveRecord::Base.configurations = configurations
@ -1654,7 +1691,7 @@ module ActiveRecord
class DatabaseTasksCheckSchemaFileMethods < ActiveRecord::TestCase
setup do
@configurations = { "development" => { "database" => "my-db" } }
@configurations = { "development" => { "adapter" => "abstract", "database" => "my-db" } }
end
def test_check_dump_filename_defaults
@ -1690,7 +1727,10 @@ module ActiveRecord
def test_check_dump_filename_defaults_for_non_primary_databases
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "/tmp") do
configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"development" => {
"primary" => { "adapter" => "abstract", "database" => "dev-db" },
"secondary" => { "adapter" => "abstract", "database" => "secondary-dev-db" },
},
}
with_stubbed_configurations(configurations) do
assert_equal "/tmp/secondary_schema.rb", ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(config_for("development", "secondary"))
@ -1701,7 +1741,7 @@ module ActiveRecord
def test_setting_schema_dump_to_nil
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "/tmp") do
configurations = {
"development" => { "primary" => { "database" => "dev-db", "schema_dump" => false } },
"development" => { "primary" => { "adapter" => "abstract", "database" => "dev-db", "schema_dump" => false } },
}
with_stubbed_configurations(configurations) do
assert_nil ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(config_for("development", "primary"))
@ -1714,7 +1754,10 @@ module ActiveRecord
ENV["SCHEMA"] = "schema_path"
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "/tmp") do
configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"development" => {
"primary" => { "adapter" => "abstract", "database" => "dev-db" },
"secondary" => { "adapter" => "abstract", "database" => "secondary-dev-db" },
},
}
with_stubbed_configurations(configurations) do
assert_equal "schema_path", ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(config_for("development", "secondary"))
@ -1728,7 +1771,10 @@ module ActiveRecord
define_method("test_check_dump_filename_for_#{fmt}_format_with_non_primary_databases") do
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "/tmp") do
configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"development" => {
"primary" => { "adapter" => "abstract", "database" => "dev-db" },
"secondary" => { "adapter" => "abstract", "database" => "secondary-dev-db" },
},
}
with_stubbed_configurations(configurations) do
assert_equal "/tmp/secondary_#{filename}", ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(config_for("development", "secondary"), fmt)

View File

@ -33,7 +33,7 @@ module ARTest
end
connection[name]["database"] ||= dbname
connection[name]["adapter"] ||= adapter
connection[name]["adapter"] ||= adapter == "sqlite3_mem" ? "sqlite3" : adapter
end
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
class FakeActiveRecordAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
attr_accessor :data_sources, :primary_keys
@columns = Hash.new { |h, k| h[k] = [] }
class << self
attr_reader :columns
end
def initialize(...)
super
@data_sources = []
@primary_keys = {}
@columns = self.class.columns
end
def primary_key(table)
@primary_keys[table] || "id"
end
def merge_column(table_name, name, sql_type = nil, options = {})
@columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s,
options[:default],
fetch_type_metadata(sql_type),
options[:null],
)
end
def columns(table_name)
@columns[table_name]
end
def data_source_exists?(*)
true
end
def active?
true
end
end

View File

@ -16,8 +16,8 @@ module Rails
def start
adapter_class.dbconsole(db_config, @options)
rescue NotImplementedError
abort "Unknown command-line client for #{db_config.database}."
rescue NotImplementedError, ActiveRecord::AdapterNotFound, LoadError => error
abort error.message
end
def db_config
@ -50,11 +50,9 @@ module Rails
private
def adapter_class
if ActiveRecord::Base.respond_to?(db_config.adapter_class_method)
ActiveRecord::Base.public_send(db_config.adapter_class_method)
else
ActiveRecord::ConnectionAdapters::AbstractAdapter
end
ActiveRecord::ConnectionAdapters.resolve(db_config.adapter)
rescue LoadError, ActiveRecord::AdapterNotFound
ActiveRecord::ConnectionAdapters::AbstractAdapter
end
def configurations # :doc:

View File

@ -120,7 +120,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
def test_unknown_command_line_client
start(adapter: "unknown", database: "db")
assert aborted
assert_match(/Unknown command-line client for db/, output)
assert_match(/database configuration specifies nonexistent 'unknown' adapter/, output)
end
def test_primary_is_automatically_picked_with_3_level_configuration
@ -153,9 +153,10 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
sample_config = {
"test" => {
"primary" => {},
"primary" => { "adapter" => "sqlite3" },
"primary_replica" => {
"replica" => true
"adapter" => "sqlite3",
"replica" => true,
}
}
}
@ -210,10 +211,9 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
attr_reader :dbconsole
def start(config = {}, argv = [])
hash_config = ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", config)
@dbconsole = Rails::DBConsole.new(parse_arguments(argv))
@dbconsole.stub(:db_config, hash_config) do
hash_config = nil
@dbconsole.stub(:db_config, -> { hash_config ||= ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", config) }) do
capture_abort { @dbconsole.start }
end
end

View File

@ -49,6 +49,7 @@ module GeneratorsTestHelper
ActiveRecord::Base.configurations = {
test: {
"#{database_name}": {
adapter: "sqlite3",
database: "db/#{database_name}.sqlite3",
migrations_paths: "db/#{database_name}_migrate",
},