Skip to content

Commit 5ed3f60

Browse files
adrianna-chang-shopifytenderlovearobenalimitmm1
committed
Introduce adapter for Trilogy, a MySQL-compatible DB client
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 <[email protected]> Co-authored-by: Adam Roben <[email protected]> Co-authored-by: Ali Ibrahim <[email protected]> Co-authored-by: Aman Gupta <[email protected]> Co-authored-by: Arthur Nogueira Neves <[email protected]> Co-authored-by: Arthur Schreiber <[email protected]> Co-authored-by: Ashe Connor <[email protected]> Co-authored-by: Brandon Keepers <[email protected]> Co-authored-by: Brian Lopez <[email protected]> Co-authored-by: Brooke Kuhlmann <[email protected]> Co-authored-by: Bryana Knight <[email protected]> Co-authored-by: Carl Brasic <[email protected]> Co-authored-by: Chris Bloom <[email protected]> Co-authored-by: Cliff Pruitt <[email protected]> Co-authored-by: Daniel Colson <[email protected]> Co-authored-by: David Calavera <[email protected]> Co-authored-by: David Celis <[email protected]> Co-authored-by: David Ratajczak <[email protected]> Co-authored-by: Dirkjan Bussink <[email protected]> Co-authored-by: Eileen Uchitelle <[email protected]> Co-authored-by: Enrique Gonzalez <[email protected]> Co-authored-by: Garrett Bjerkhoel <[email protected]> Co-authored-by: Georgi Knox <[email protected]> Co-authored-by: HParker <[email protected]> Co-authored-by: Hailey Somerville <[email protected]> Co-authored-by: James Dennes <[email protected]> Co-authored-by: Jane Sternbach <[email protected]> Co-authored-by: Jess Bees <[email protected]> Co-authored-by: Jesse Toth <[email protected]> Co-authored-by: Joel Hawksley <[email protected]> Co-authored-by: John Barnette <[email protected]> Co-authored-by: John Crepezzi <[email protected]> Co-authored-by: John Hawthorn <[email protected]> Co-authored-by: John Nunemaker <[email protected]> Co-authored-by: Jonathan Hoyt <[email protected]> Co-authored-by: Katrina Owen <[email protected]> Co-authored-by: Keeran Raj Hawoldar <[email protected]> Co-authored-by: Kevin Solorio <[email protected]> Co-authored-by: Leo Correa <[email protected]> Co-authored-by: Lizz Hale <[email protected]> Co-authored-by: Lorin Thwaits <[email protected]> Co-authored-by: Matt Jones <[email protected]> Co-authored-by: Matthew Draper <[email protected]> Co-authored-by: Max Veytsman <[email protected]> Co-authored-by: Nathan Witmer <[email protected]> Co-authored-by: Nick Holden <[email protected]> Co-authored-by: Paarth Madan <[email protected]> Co-authored-by: Patrick Reynolds <[email protected]> Co-authored-by: Rob Sanheim <[email protected]> Co-authored-by: Rocio Delgado <[email protected]> Co-authored-by: Sam Lambert <[email protected]> Co-authored-by: Shay Frendt <[email protected]> Co-authored-by: Shlomi Noach <[email protected]> Co-authored-by: Sophie Haskins <[email protected]> Co-authored-by: Thomas Maurer <[email protected]> Co-authored-by: Tim Pease <[email protected]> Co-authored-by: Yossef Mendelssohn <[email protected]> Co-authored-by: Zack Koppert <[email protected]> Co-authored-by: Zhongying Qiao <[email protected]>
1 parent bd8aeea commit 5ed3f60

File tree

68 files changed

+1735
-161
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1735
-161
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ platforms :ruby, :windows do
149149
group :db do
150150
gem "pg", "~> 1.3"
151151
gem "mysql2", "~> 0.5"
152+
gem "trilogy", "~> 2.4"
152153
end
153154
end
154155

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ GEM
507507
timeout (0.3.2)
508508
tomlrb (2.0.3)
509509
trailblazer-option (0.1.2)
510+
trilogy (2.4.0)
510511
turbo-rails (1.3.2)
511512
actionpack (>= 6.0.0)
512513
activejob (>= 6.0.0)
@@ -617,6 +618,7 @@ DEPENDENCIES
617618
sucker_punch
618619
tailwindcss-rails
619620
terser (>= 1.1.4)
621+
trilogy (~> 2.4)
620622
turbo-rails
621623
tzinfo-data
622624
w3c_validators (~> 1.3.6)

activerecord/CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
* Introduce adapter for Trilogy database client
2+
3+
Trilogy is a MySQL-compatible database client. Rails applications can use Trilogy
4+
by configuring their `config/database.yml`:
5+
6+
```yaml
7+
development:
8+
adapter: trilogy
9+
database: blog_development
10+
pool: 5
11+
```
12+
13+
Or by using the `DATABASE_URL` environment variable:
14+
15+
```ruby
16+
ENV['DATABASE_URL'] # => "trilogy://localhost/blog_development?pool=5"
17+
```
18+
19+
*Adrianna Chang*
20+
121
* `after_commit` callbacks defined on models now execute in the correct order.
222

323
```ruby

activerecord/RUNNING_UNIT_TESTS.rdoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ example:
2121
Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:
2222

2323
$ bundle exec rake test:mysql2
24+
$ bundle exec rake test:trilogy
2425
$ bundle exec rake test:postgresql
2526
$ bundle exec rake test:sqlite3
2627

activerecord/Rakefile

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,24 @@ def run_without_aborting(*tasks)
1818
abort "Errors running #{errors.join(', ')}" if errors.any?
1919
end
2020

21-
desc "Run mysql2, sqlite, and postgresql tests by default"
21+
desc "Run mysql2, trilogy, sqlite, and postgresql tests by default"
2222
task default: :test
2323

2424
task :package
2525

26-
desc "Run mysql2, sqlite, and postgresql tests"
26+
desc "Run mysql2, trilogy, sqlite, and postgresql tests"
2727
task :test do
2828
tasks = defined?(JRUBY_VERSION) ?
2929
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
30-
%w(test_mysql2 test_sqlite3 test_postgresql)
30+
%w(test_mysql2 test_trilogy test_sqlite3 test_postgresql)
3131
run_without_aborting(*tasks)
3232
end
3333

3434
namespace :test do
3535
task :isolated do
3636
tasks = defined?(JRUBY_VERSION) ?
3737
%w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
38-
%w(isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql)
38+
%w(isolated_test_mysql2 isolated_test_trilogy isolated_test_sqlite3 isolated_test_postgresql)
3939
run_without_aborting(*tasks)
4040
end
4141

@@ -56,18 +56,19 @@ namespace :db do
5656
task drop: ["db:mysql:drop", "db:postgresql:drop"]
5757
end
5858

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

6969
t.test_files = files
7070

71+
t.test_files = files
7172
t.warning = true
7273
t.verbose = true
7374
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)

activerecord/bin/test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module Minitest
1515
opts.separator ""
1616
opts.separator "Active Record options:"
1717
opts.on("-a", "--adapter [ADAPTER]",
18-
"Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql2, postgresql)") do |adapter|
18+
"Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql2, trilogy, postgresql)") do |adapter|
1919
ENV["ARCONN"] = adapter.strip
2020
end
2121

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveRecord
4+
module ConnectionAdapters
5+
module Trilogy
6+
module Errors
7+
# ServerShutdown will be raised when the database server was shutdown.
8+
class ServerShutdown < ActiveRecord::ConnectionFailed
9+
end
10+
11+
# ServerLost will be raised when the database connection was lost.
12+
class ServerLost < ActiveRecord::ConnectionFailed
13+
end
14+
15+
# ServerGone will be raised when the database connection is gone.
16+
class ServerGone < ActiveRecord::ConnectionFailed
17+
end
18+
19+
# BrokenPipe will be raised when a system process connection fails.
20+
class BrokenPipe < ActiveRecord::ConnectionFailed
21+
end
22+
23+
# SocketError will be raised when Ruby encounters a network error.
24+
class SocketError < ActiveRecord::ConnectionFailed
25+
end
26+
27+
# ConnectionResetByPeer will be raised when a network connection is closed
28+
# outside the sytstem process.
29+
class ConnectionResetByPeer < ActiveRecord::ConnectionFailed
30+
end
31+
32+
# ClosedConnection will be raised when the Trilogy encounters a closed
33+
# connection.
34+
class ClosedConnection < ActiveRecord::ConnectionFailed
35+
end
36+
37+
# InvalidSequenceId will be raised when Trilogy ecounters an invalid sequence
38+
# id.
39+
class InvalidSequenceId < ActiveRecord::ConnectionFailed
40+
end
41+
42+
# UnexpectedPacket will be raised when Trilogy ecounters an unexpected
43+
# response packet.
44+
class UnexpectedPacket < ActiveRecord::ConnectionFailed
45+
end
46+
end
47+
end
48+
end
49+
end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveRecord
4+
module ConnectionAdapters
5+
module Trilogy
6+
class LostConnectionExceptionTranslator
7+
attr_reader :exception, :message, :error_number
8+
9+
def initialize(exception, message, error_number)
10+
@exception = exception
11+
@message = message
12+
@error_number = error_number
13+
end
14+
15+
def translate
16+
translate_database_exception || translate_ruby_exception || translate_trilogy_exception
17+
end
18+
19+
private
20+
ER_SERVER_SHUTDOWN = 1053
21+
CR_SERVER_LOST = 2013
22+
CR_SERVER_LOST_EXTENDED = 2055
23+
CR_SERVER_GONE_ERROR = 2006
24+
25+
def translate_database_exception
26+
case error_number
27+
when ER_SERVER_SHUTDOWN
28+
Errors::ServerShutdown.new(message)
29+
when CR_SERVER_LOST, CR_SERVER_LOST_EXTENDED
30+
Errors::ServerLost.new(message)
31+
when CR_SERVER_GONE_ERROR
32+
Errors::ServerGone.new(message)
33+
end
34+
end
35+
36+
def translate_ruby_exception
37+
case exception
38+
when Errno::EPIPE
39+
Errors::BrokenPipe.new(message)
40+
when SocketError, IOError
41+
Errors::SocketError.new(message)
42+
when ::Trilogy::ConnectionError
43+
if message.include?("Connection reset by peer")
44+
Errors::ConnectionResetByPeer.new(message)
45+
end
46+
end
47+
end
48+
49+
def translate_trilogy_exception
50+
return unless exception.is_a?(::Trilogy::Error)
51+
52+
case message
53+
when /TRILOGY_CLOSED_CONNECTION/
54+
Errors::ClosedConnection.new(message)
55+
when /TRILOGY_INVALID_SEQUENCE_ID/
56+
Errors::InvalidSequenceId.new(message)
57+
when /TRILOGY_UNEXPECTED_PACKET/
58+
Errors::UnexpectedPacket.new(message)
59+
end
60+
end
61+
end
62+
end
63+
end
64+
end

0 commit comments

Comments
 (0)