Skip to content

Commit

Permalink
Make this gem work with mysql2 and trilogy
Browse files Browse the repository at this point in the history
Notes:

- Trilogy does not like integers as DB passwords so we must now pass string literals as the password(s) in our database ymls.
- `mysql2` has a `select_db` method but the equivalent method in `trilogy` is `change_db`
- `Trilogy` will raise an `ActiveRecord::StatementInvalid` error when attempting to use an invalid connection

Co-authored-by: Benjamin Quorning <bquorning@zendesk.com>

Co-authored-by: Benjamin Quorning <bquorning@zendesk.com>
  • Loading branch information
HeyNonster and bquorning committed Jun 26, 2023
1 parent c139cec commit f33701c
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 54 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Expand Up @@ -6,6 +6,7 @@ and as of v1.0.0 this project adheres to [Semantic Versioning](https://semver.or

## [Unreleased]

- Remove `mysql2` as a direct dependency, test Rails 7.0 with `mysql2` and `activerecord-trilogy-adapter`
- Drop Ruby 2.6.
- Start testing with Ruby 3.2.

Expand Down
2 changes: 1 addition & 1 deletion Readme.md
Expand Up @@ -43,7 +43,7 @@ test_pool_2 => 127.0.0.1/3306//root/false

## Support

For now, the only backend known to work is MySQL, with the mysql2 gem.
For now, the only backend known to work is MySQL, with the mysql2 or activerecord-trilogy-adapter gem.
Postgres, from an informal reading of the docs, will never support the concept of one server connection sharing multiple dbs.

## Installation
Expand Down
17 changes: 14 additions & 3 deletions lib/active_record_host_pool.rb
@@ -1,5 +1,19 @@
# frozen_string_literal: true

module ActiveRecordHostPool
class << self
attr_accessor :loaded_db_adapter
end
end

begin
require 'mysql2'
ActiveRecordHostPool.loaded_db_adapter = :mysql2
rescue LoadError
require 'activerecord-trilogy-adapter'
ActiveRecordHostPool.loaded_db_adapter = :trilogy
end

require 'active_record'
require 'active_record/base'
require 'active_record/connection_adapters/abstract_adapter'
Expand All @@ -9,6 +23,3 @@
require 'active_record_host_pool/pool_proxy'
require 'active_record_host_pool/connection_adapter_mixin'
require 'active_record_host_pool/version'

module ActiveRecordHostPool
end
29 changes: 26 additions & 3 deletions lib/active_record_host_pool/connection_adapter_mixin.rb
@@ -1,6 +1,13 @@
# frozen_string_literal: true

require "active_record/connection_adapters/mysql2_adapter"
case ActiveRecordHostPool.loaded_db_adapter
when :mysql2
require "active_record/connection_adapters/mysql2_adapter"
when :trilogy
require 'trilogy_adapter/connection'
require "trilogy_adapter/errors"
ActiveRecord::Base.extend TrilogyAdapter::Connection
end

module ActiveRecordHostPool
module DatabaseSwitch
Expand Down Expand Up @@ -75,13 +82,24 @@ def _switch_connection
)
log("select_db #{_host_pool_current_database}", "SQL") do
clear_cache! if respond_to?(:clear_cache!)
raw_connection.select_db(_host_pool_current_database)
_arhp_select_db(_host_pool_current_database)
end
@_cached_current_database = _host_pool_current_database
@_cached_connection_object_id = @connection.object_id
end
end

case ActiveRecordHostPool.loaded_db_adapter
when :mysql2
def _arhp_select_db(database)
raw_connection.select_db(database)
end
when :trilogy
def _arhp_select_db(database) # rubocop:disable Lint/DuplicateMethods
raw_connection.change_db(database)
end
end

# prevent different databases from sharing the same query cache
def cache_sql(sql, *args)
super(_host_pool_current_database.to_s + "/" + sql, *args)
Expand Down Expand Up @@ -122,7 +140,12 @@ def establish_connection(spec)
end
end

ActiveRecord::ConnectionAdapters::Mysql2Adapter.include(ActiveRecordHostPool::DatabaseSwitch)
case ActiveRecordHostPool.loaded_db_adapter
when :mysql2
ActiveRecord::ConnectionAdapters::Mysql2Adapter.include(ActiveRecordHostPool::DatabaseSwitch)
when :trilogy
ActiveRecord::ConnectionAdapters::TrilogyAdapter.include(ActiveRecordHostPool::DatabaseSwitch)
end

# In Rails 6.1 Connection Pools are no longer instantiated in #establish_connection but in a
# new pool method.
Expand Down
9 changes: 8 additions & 1 deletion lib/active_record_host_pool/pool_proxy_6_1.rb
Expand Up @@ -15,6 +15,13 @@
module ActiveRecordHostPool
# Sits between ConnectionHandler and a bunch of different ConnectionPools (one per host).
class PoolProxy < Delegator
case ActiveRecordHostPool.loaded_db_adapter
when :mysql2
RESCUABLE_DB_ERROR = Mysql2::Error
when :trilogy
RESCUABLE_DB_ERROR = Trilogy::ProtocolError
end

def initialize(pool_config)
super(pool_config)
@pool_config = pool_config
Expand All @@ -36,7 +43,7 @@ def __setobj__(pool_config)
def connection(*args)
real_connection = _unproxied_connection(*args)
_connection_proxy_for(real_connection, @config[:database])
rescue Mysql2::Error, ActiveRecord::NoDatabaseError
rescue RESCUABLE_DB_ERROR, ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
_connection_pools.delete(_pool_key)
Kernel.raise
end
Expand Down
32 changes: 17 additions & 15 deletions test/database.yml
@@ -1,4 +1,6 @@
<% mysql = URI(ENV['MYSQL_URL'] || 'mysql://root@127.0.0.1:3306') %>
<% adapter = ActiveRecordHostPool.loaded_db_adapter %>

# ARHP creates separate connection pools based on the pool key.
# The pool key is defined as:
# host / port / socket / username / replica
Expand Down Expand Up @@ -28,65 +30,65 @@
# test_pool_2 => 127.0.0.1/3306//root/false

test_pool_1_db_a:
adapter: mysql2
adapter: <%= adapter %>
encoding: utf8
database: arhp_test_db_a
username: <%= mysql.user %>
password: <%= mysql.password %>
password: "<%= mysql.password %>"
host: <%= mysql.host %>
reconnect: true

# Mimic configurations as read by active_record_shards/ar_flexmaster
test_pool_1_db_a_replica:
adapter: mysql2
adapter: <%= adapter %>
encoding: utf8
database: arhp_test_db_a_replica
username: <%= mysql.user %>
password: <%= mysql.password %>
password: "<%= mysql.password %>"
host: <%= mysql.host %>
reconnect: true
slave: true

test_pool_1_db_b:
adapter: mysql2
adapter: <%= adapter %>
encoding: utf8
database: arhp_test_db_b
username: <%= mysql.user %>
password: <%= mysql.password %>
password: "<%= mysql.password %>"
host: <%= mysql.host %>
reconnect: true

test_pool_1_db_not_there:
adapter: mysql2
adapter: <%= adapter %>
encoding: utf8
database: arhp_test_db_not_there
username: <%= mysql.user %>
password: <%= mysql.password %>
password: "<%= mysql.password %>"
host: <%= mysql.host %>
reconnect: true

test_pool_2_db_d:
adapter: mysql2
adapter: <%= adapter %>
encoding: utf8
database: arhp_test_db_d
username: <%= mysql.user %>
password: <%= mysql.password %>
password: "<%= mysql.password %>"
host: <%= mysql.host %>
port: <%= mysql.port %>
reconnect: true

test_pool_2_db_e:
adapter: mysql2
adapter: <%= adapter %>
encoding: utf8
database: arhp_test_db_e
username: <%= mysql.user %>
password: <%= mysql.password %>
password: "<%= mysql.password %>"
host: <%= mysql.host %>
port: <%= mysql.port %>
reconnect: true

test_pool_3_db_e:
adapter: mysql2
adapter: <%= adapter %>
encoding: utf8
database: arhp_test_db_e
username: john-doe
Expand All @@ -99,10 +101,10 @@ test_pool_3_db_e:
# otherwise the test_models_with_matching_hosts_and_non_matching_databases_issue_exists_without_arhp_patch
# test fails
test_pool_1_db_c:
adapter: mysql2
adapter: <%= adapter %>
encoding: utf8
database: arhp_test_db_c
username: <%= mysql.user %>
password: <%= mysql.password %>
password: "<%= mysql.password %>"
host: <%= mysql.host %>
reconnect: true
8 changes: 7 additions & 1 deletion test/test_arhp_caching.rb
Expand Up @@ -63,7 +63,13 @@ def test_models_with_matching_hosts_and_non_matching_databases_issue_exists_with
end

cached_db = Pool1DbC.connection.unproxied.pool.connections.first.instance_variable_get(:@_cached_current_database)
assert_equal("Mysql2::Error: Table '#{cached_db}.pool1_db_cs' doesn't exist", exception.message)

case ActiveRecordHostPool.loaded_db_adapter
when :mysql2
assert_equal("Mysql2::Error: Table '#{cached_db}.pool1_db_cs' doesn't exist", exception.message)
when :trilogy
assert_equal("Trilogy::ProtocolError: 1146: Table '#{cached_db}.pool1_db_cs' doesn't exist", exception.message)
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion test/test_arhp_wrong_db.rb
Expand Up @@ -10,10 +10,10 @@ def setup
else
Phenix.load_database_config
end
ActiveRecordHostPool::PoolProxy.class_variable_set(:@@_connection_pools, {})
end

def teardown
ActiveRecordHostPool::PoolProxy.class_variable_set(:@@_connection_pools, {})
Phenix.burn!
end

Expand Down

0 comments on commit f33701c

Please sign in to comment.