Skip to content

Commit

Permalink
Introduce adapter for Trilogy, a MySQL-compatible DB client
Browse files Browse the repository at this point in the history
The [Trilogy database client][trilogy-client] and corresponding
[Active Record adapter][ar-adapter] were both open sourced by GitHub last year.

Shopify has recently taken the plunge and successfully adopted Trilogy in their Rails monolith.
With two major Rails applications running Trilogy successfully, we'd like to propose upstreaming the adapter
to Rails as a MySQL-compatible alternative to Mysql2Adapter.

[trilogy-client]: https://github.com/github/trilogy
[ar-adapter]: https://github.com/github/activerecord-trilogy-adapter

Co-authored-by: Aaron Patterson <tenderlove@github.com>
Co-authored-by: Adam Roben <adam@roben.org>
Co-authored-by: Ali Ibrahim <aibrahim2k2@gmail.com>
Co-authored-by: Aman Gupta <aman@tmm1.net>
Co-authored-by: Arthur Nogueira Neves <github@arthurnn.com>
Co-authored-by: Arthur Schreiber <arthurschreiber@github.com>
Co-authored-by: Ashe Connor <kivikakk@github.com>
Co-authored-by: Brandon Keepers <brandon@opensoul.org>
Co-authored-by: Brian Lopez <seniorlopez@gmail.com>
Co-authored-by: Brooke Kuhlmann <brooke@testdouble.com>
Co-authored-by: Bryana Knight <bryanaknight@github.com>
Co-authored-by: Carl Brasic <brasic@github.com>
Co-authored-by: Chris Bloom <chrisbloom7@github.com>
Co-authored-by: Cliff Pruitt <cliff.pruitt@cliffpruitt.com>
Co-authored-by: Daniel Colson <composerinteralia@github.com>
Co-authored-by: David Calavera <david.calavera@gmail.com>
Co-authored-by: David Celis <davidcelis@github.com>
Co-authored-by: David Ratajczak <david@mockra.com>
Co-authored-by: Dirkjan Bussink <d.bussink@gmail.com>
Co-authored-by: Eileen Uchitelle <eileencodes@gmail.com>
Co-authored-by: Enrique Gonzalez <enriikke@gmail.com>
Co-authored-by: Garrett Bjerkhoel <garrett@github.com>
Co-authored-by: Georgi Knox <georgicodes@github.com>
Co-authored-by: HParker <HParker@github.com>
Co-authored-by: Hailey Somerville <hailey@hailey.lol>
Co-authored-by: James Dennes <jdennes@gmail.com>
Co-authored-by: Jane Sternbach <janester@github.com>
Co-authored-by: Jess Bees <toomanybees@github.com>
Co-authored-by: Jesse Toth <jesse.toth@github.com>
Co-authored-by: Joel Hawksley <joelhawksley@github.com>
Co-authored-by: John Barnette <jbarnette@github.com>
Co-authored-by: John Crepezzi <john.crepezzi@gmail.com>
Co-authored-by: John Hawthorn <john@hawthorn.email>
Co-authored-by: John Nunemaker <nunemaker@gmail.com>
Co-authored-by: Jonathan Hoyt <hoyt@github.com>
Co-authored-by: Katrina Owen <kytrinyx@github.com>
Co-authored-by: Keeran Raj Hawoldar <keeran@gmail.com>
Co-authored-by: Kevin Solorio <soloriok@gmail.com>
Co-authored-by: Leo Correa <lcorr005@gmail.com>
Co-authored-by: Lizz Hale <lizzhale@github.com>
Co-authored-by: Lorin Thwaits <lorint@gmail.com>
Co-authored-by: Matt Jones <al2o3cr@gmail.com>
Co-authored-by: Matthew Draper <matthewd@github.com>
Co-authored-by: Max Veytsman <mveytsman@github.com>
Co-authored-by: Nathan Witmer <nathan@zerowidth.com>
Co-authored-by: Nick Holden <nick.r.holden@gmail.com>
Co-authored-by: Paarth Madan <paarth.madan@shopify.com>
Co-authored-by: Patrick Reynolds <patrick.reynolds@github.com>
Co-authored-by: Rob Sanheim <rsanheim@gmail.com>
Co-authored-by: Rocio Delgado <rocio@github.com>
Co-authored-by: Sam Lambert <sam.lambert@github.com>
Co-authored-by: Shay Frendt <shay@github.com>
Co-authored-by: Shlomi Noach <shlomi-noach@github.com>
Co-authored-by: Sophie Haskins <sophaskins@github.com>
Co-authored-by: Thomas Maurer <tma@github.com>
Co-authored-by: Tim Pease <tim.pease@gmail.com>
Co-authored-by: Yossef Mendelssohn <ymendel@pobox.com>
Co-authored-by: Zack Koppert <zkoppert@github.com>
Co-authored-by: Zhongying Qiao <cryptoque@users.noreply.github.com>
  • Loading branch information
Show file tree
Hide file tree
Showing 68 changed files with 1,735 additions and 161 deletions.
1 change: 1 addition & 0 deletions Gemfile
Expand Up @@ -149,6 +149,7 @@ platforms :ruby, :windows do
group :db do
gem "pg", "~> 1.3"
gem "mysql2", "~> 0.5"
gem "trilogy", "~> 2.4"
end
end

Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Expand Up @@ -507,6 +507,7 @@ GEM
timeout (0.3.2)
tomlrb (2.0.3)
trailblazer-option (0.1.2)
trilogy (2.4.0)
turbo-rails (1.3.2)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
Expand Down Expand Up @@ -617,6 +618,7 @@ DEPENDENCIES
sucker_punch
tailwindcss-rails
terser (>= 1.1.4)
trilogy (~> 2.4)
turbo-rails
tzinfo-data
w3c_validators (~> 1.3.6)
Expand Down
20 changes: 20 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,23 @@
* Introduce adapter for Trilogy database client

Trilogy is a MySQL-compatible database client. Rails applications can use Trilogy
by configuring their `config/database.yml`:

```yaml
development:
adapter: trilogy
database: blog_development
pool: 5
```

Or by using the `DATABASE_URL` environment variable:

```ruby
ENV['DATABASE_URL'] # => "trilogy://localhost/blog_development?pool=5"
```

*Adrianna Chang*

* `after_commit` callbacks defined on models now execute in the correct order.

```ruby
Expand Down
1 change: 1 addition & 0 deletions activerecord/RUNNING_UNIT_TESTS.rdoc
Expand Up @@ -21,6 +21,7 @@ example:
Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:

$ bundle exec rake test:mysql2
$ bundle exec rake test:trilogy
$ bundle exec rake test:postgresql
$ bundle exec rake test:sqlite3

Expand Down
13 changes: 7 additions & 6 deletions activerecord/Rakefile
Expand Up @@ -18,24 +18,24 @@ def run_without_aborting(*tasks)
abort "Errors running #{errors.join(', ')}" if errors.any?
end

desc "Run mysql2, sqlite, and postgresql tests by default"
desc "Run mysql2, trilogy, sqlite, and postgresql tests by default"
task default: :test

task :package

desc "Run mysql2, sqlite, and postgresql tests"
desc "Run mysql2, trilogy, sqlite, and postgresql tests"
task :test do
tasks = defined?(JRUBY_VERSION) ?
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
%w(test_mysql2 test_sqlite3 test_postgresql)
%w(test_mysql2 test_trilogy test_sqlite3 test_postgresql)
run_without_aborting(*tasks)
end

namespace :test do
task :isolated do
tasks = defined?(JRUBY_VERSION) ?
%w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
%w(isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql)
%w(isolated_test_mysql2 isolated_test_trilogy isolated_test_sqlite3 isolated_test_postgresql)
run_without_aborting(*tasks)
end

Expand All @@ -56,18 +56,19 @@ namespace :db do
task drop: ["db:mysql:drop", "db:postgresql:drop"]
end

%w( mysql2 postgresql sqlite3 sqlite3_mem oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
%w( mysql2 trilogy postgresql sqlite3 sqlite3_mem oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
namespace :test do
Rake::TestTask.new(adapter => "#{adapter}:env") do |t|
adapter_short = adapter[/^[a-z0-9]+/]
t.libs << "test"
files = (FileList["test/cases/**/*_test.rb"].reject {
|x| x.include?("/adapters/") || x.include?("/encryption/performance")
} + FileList["test/cases/adapters/#{adapter_short}/**/*_test.rb"])
files = files + FileList["test/cases/adapters/abstract_mysql_adapter/**/*_test.rb"] if adapter == "mysql2"
files = files + FileList["test/cases/adapters/abstract_mysql_adapter/**/*_test.rb"] if ["mysql2", "trilogy"].include?(adapter)

t.test_files = files

t.test_files = files
t.warning = true
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
Expand Down
2 changes: 1 addition & 1 deletion activerecord/bin/test
Expand Up @@ -15,7 +15,7 @@ module Minitest
opts.separator ""
opts.separator "Active Record options:"
opts.on("-a", "--adapter [ADAPTER]",
"Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql2, postgresql)") do |adapter|
"Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql2, trilogy, postgresql)") do |adapter|
ENV["ARCONN"] = adapter.strip
end

Expand Down
@@ -0,0 +1,49 @@
# frozen_string_literal: true

module ActiveRecord
module ConnectionAdapters
module Trilogy
module Errors
# ServerShutdown will be raised when the database server was shutdown.
class ServerShutdown < ActiveRecord::ConnectionFailed
end

# ServerLost will be raised when the database connection was lost.
class ServerLost < ActiveRecord::ConnectionFailed
end

# ServerGone will be raised when the database connection is gone.
class ServerGone < ActiveRecord::ConnectionFailed
end

# BrokenPipe will be raised when a system process connection fails.
class BrokenPipe < ActiveRecord::ConnectionFailed
end

# SocketError will be raised when Ruby encounters a network error.
class SocketError < ActiveRecord::ConnectionFailed
end

# ConnectionResetByPeer will be raised when a network connection is closed
# outside the sytstem process.
class ConnectionResetByPeer < ActiveRecord::ConnectionFailed
end

# ClosedConnection will be raised when the Trilogy encounters a closed
# connection.
class ClosedConnection < ActiveRecord::ConnectionFailed
end

# InvalidSequenceId will be raised when Trilogy ecounters an invalid sequence
# id.
class InvalidSequenceId < ActiveRecord::ConnectionFailed
end

# UnexpectedPacket will be raised when Trilogy ecounters an unexpected
# response packet.
class UnexpectedPacket < ActiveRecord::ConnectionFailed
end
end
end
end
end
@@ -0,0 +1,64 @@
# frozen_string_literal: true

module ActiveRecord
module ConnectionAdapters
module Trilogy
class LostConnectionExceptionTranslator
attr_reader :exception, :message, :error_number

def initialize(exception, message, error_number)
@exception = exception
@message = message
@error_number = error_number
end

def translate
translate_database_exception || translate_ruby_exception || translate_trilogy_exception
end

private
ER_SERVER_SHUTDOWN = 1053
CR_SERVER_LOST = 2013
CR_SERVER_LOST_EXTENDED = 2055
CR_SERVER_GONE_ERROR = 2006

def translate_database_exception
case error_number
when ER_SERVER_SHUTDOWN
Errors::ServerShutdown.new(message)
when CR_SERVER_LOST, CR_SERVER_LOST_EXTENDED
Errors::ServerLost.new(message)
when CR_SERVER_GONE_ERROR
Errors::ServerGone.new(message)
end
end

def translate_ruby_exception
case exception
when Errno::EPIPE
Errors::BrokenPipe.new(message)
when SocketError, IOError
Errors::SocketError.new(message)
when ::Trilogy::ConnectionError
if message.include?("Connection reset by peer")
Errors::ConnectionResetByPeer.new(message)
end
end
end

def translate_trilogy_exception
return unless exception.is_a?(::Trilogy::Error)

case message
when /TRILOGY_CLOSED_CONNECTION/
Errors::ClosedConnection.new(message)
when /TRILOGY_INVALID_SEQUENCE_ID/
Errors::InvalidSequenceId.new(message)
when /TRILOGY_UNEXPECTED_PACKET/
Errors::UnexpectedPacket.new(message)
end
end
end
end
end
end

0 comments on commit 5ed3f60

Please sign in to comment.