Raise specific exception when a connection is not defined

Co-authored-by: Matthew Draper <matthewd@github.com>
This commit is contained in:
Hana Harencarova 2024-06-06 05:32:33 +00:00 committed by GitHub
parent 7b1ceb7659
commit 0bdf44d921
8 changed files with 86 additions and 21 deletions

@ -1,3 +1,9 @@
* Raise specific exception when a connection is not defined.
The new `ConnectionNotDefined` exception provides connection name, shard and role accessors indicating the details of the connection that was requested.
*Hana Harencarova*, *Matthew Draper*
* Delete the deprecated constant `ActiveRecord::ImmutableRelation`.
*Xavier Noria*

@ -210,18 +210,25 @@ def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_rol
# This makes retrieving the connection pool O(1) once the process is warm.
# When a connection is established or removed, we invalidate the cache.
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
pool_manager = get_pool_manager(connection_name)
pool = pool_manager&.get_pool_config(role, shard)&.pool
if strict && !pool
if shard != ActiveRecord::Base.default_shard
message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
elsif role != ActiveRecord::Base.default_role
message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
else
message = "No connection pool for '#{connection_name}' found."
end
selector = [
("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
("'#{role}' role" unless role == ActiveRecord::Base.default_role),
].compact.join(" and ")
raise ConnectionNotEstablished, message
selector = [
(connection_name unless connection_name == "ActiveRecord::Base"),
selector.presence,
].compact.join(" with ")
selector = " for #{selector}" if selector.present?
message = "No database connection defined#{selector}."
raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
end
pool

@ -1044,7 +1044,8 @@ def verified!
end
def retryable_connection_error?(exception)
exception.is_a?(ConnectionNotEstablished) || exception.is_a?(ConnectionFailed)
(exception.is_a?(ConnectionNotEstablished) && !exception.is_a?(ConnectionNotDefined)) ||
exception.is_a?(ConnectionFailed)
end
def invalidate_transaction(exception)

@ -84,6 +84,19 @@ def set_pool(connection_pool)
class ConnectionTimeoutError < ConnectionNotEstablished
end
# Raised when a database connection pool is requested but
# has not been defined.
class ConnectionNotDefined < ConnectionNotEstablished
def initialize(message = nil, connection_name: nil, role: nil, shard: nil)
super(message)
@connection_name = connection_name
@role = role
@shard = shard
end
attr_reader :connection_name, :role, :shard
end
# Raised when connection to the database could not been established because it was not
# able to connect to the host or when the authorization failed.
class DatabaseConnectionError < ConnectionNotEstablished

@ -359,13 +359,13 @@ def test_retrieve_connection_pool_with_invalid_id
end
def test_calling_connected_to_on_a_non_existent_handler_raises
error = assert_raises ActiveRecord::ConnectionNotEstablished do
error = assert_raises ActiveRecord::ConnectionNotDefined do
ActiveRecord::Base.connected_to(role: :non_existent) do
Person.first
end
end
assert_equal "No connection pool for 'ActiveRecord::Base' found for the 'non_existent' role.", error.message
assert_equal "No database connection defined for 'non_existent' role.", error.message
end
def test_default_handlers_are_writing_and_reading

@ -21,7 +21,7 @@ def test_establishing_a_connection_in_connected_to_block_uses_current_role_and_s
ActiveRecord::Base.establish_connection(db_config)
assert_nothing_raised { Person.first }
assert_equal [:default, :shard_one], ActiveRecord::Base.connection_handler.send(:get_pool_manager, "ActiveRecord::Base").shard_names
assert_equal [:default, :shard_one].sort, ActiveRecord::Base.connection_handler.send(:get_pool_manager, "ActiveRecord::Base").shard_names.sort
end
end
@ -233,13 +233,51 @@ def test_calling_connected_to_on_a_non_existent_shard_raises
default: { writing: :arunit, reading: :arunit }
})
error = assert_raises ActiveRecord::ConnectionNotEstablished do
error = assert_raises ActiveRecord::ConnectionNotDefined do
ActiveRecord::Base.connected_to(role: :reading, shard: :foo) do
Person.first
end
end
assert_equal "No connection pool for 'ActiveRecord::Base' found for the 'foo' shard.", error.message
assert_equal "No database connection defined for 'foo' shard and 'reading' role.", error.message
assert_equal "ActiveRecord::Base", error.connection_name
assert_equal :foo, error.shard
assert_equal :reading, error.role
end
def test_calling_connected_to_on_a_non_existent_role_for_shard_raises
ActiveRecord::Base.connects_to(shards: {
default: { writing: :arunit, reading: :arunit },
shard_one: { writing: :arunit, reading: :arunit }
})
error = assert_raises ActiveRecord::ConnectionNotDefined do
ActiveRecord::Base.connected_to(role: :non_existent, shard: :shard_one) do
Person.first
end
end
assert_equal "No database connection defined for 'shard_one' shard and 'non_existent' role.", error.message
assert_equal "ActiveRecord::Base", error.connection_name
assert_equal :shard_one, error.shard
assert_equal :non_existent, error.role
end
def test_calling_connected_to_on_a_default_role_for_non_existent_shard_raises
ActiveRecord::Base.connects_to(shards: {
default: { writing: :arunit, reading: :arunit }
})
error = assert_raises ActiveRecord::ConnectionNotDefined do
ActiveRecord::Base.connected_to(shard: :foo) do
Person.first
end
end
assert_equal "No database connection defined for 'foo' shard.", error.message
assert_equal "ActiveRecord::Base", error.connection_name
assert_equal :foo, error.shard
assert_equal :writing, error.role
end
def test_cannot_swap_shards_while_prohibited

@ -1710,7 +1710,7 @@ def test_only_existing_connections_are_replaced
setup_shared_connection_pool
assert_raises(ActiveRecord::ConnectionNotEstablished) do
assert_raises(ActiveRecord::ConnectionNotDefined) do
ActiveRecord::Base.connected_to(role: :reading, shard: :two) do
ActiveRecord::Base.retrieve_connection
end
@ -1721,7 +1721,7 @@ def test_only_existing_connections_are_restored
clean_up_connection_handler
teardown_shared_connection_pool
assert_raises(ActiveRecord::ConnectionNotEstablished) do
assert_raises(ActiveRecord::ConnectionNotDefined) do
ActiveRecord::Base.connected_to(role: :reading) do
ActiveRecord::Base.retrieve_connection
end

@ -23,21 +23,21 @@ def setup
end
def test_connection_no_longer_established
assert_raise(ActiveRecord::ConnectionNotEstablished) do
assert_raise(ActiveRecord::ConnectionNotDefined) do
TestRecord.find(1)
end
assert_raise(ActiveRecord::ConnectionNotEstablished) do
assert_raise(ActiveRecord::ConnectionNotDefined) do
TestRecord.new.save
end
end
def test_error_message_when_connection_not_established
error = assert_raise(ActiveRecord::ConnectionNotEstablished) do
error = assert_raise(ActiveRecord::ConnectionNotDefined) do
TestRecord.find(1)
end
assert_equal "No connection pool for 'ActiveRecord::Base' found.", error.message
assert_equal "No database connection defined.", error.message
end
def test_underlying_adapter_no_longer_active