Skip to content

Commit 98d8601

Browse files
adrianna-chang-shopifymiry
authored andcommitted
Semian + Trilogy: Introduce adapter for Trilogy AR Adapter (#468)
* Install adapter gem, bump to Rails edge * Introduce Trilogy Active Record adapter for Semian * Rubocop: Ignore multiple assertions per test * Add gemfiles for trilogy, CI steps, update changelog * Add test for Trilogy client default timeout
1 parent 57d2e0d commit 98d8601

10 files changed

+677
-14
lines changed

.github/workflows/test.yml

+3
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ jobs:
119119
- redis_4
120120
- redis_5
121121
- redis_client
122+
- activerecord_trilogy_adapter
122123
include:
123124
- gemfile: grpc
124125
adapter: grpc
@@ -134,6 +135,8 @@ jobs:
134135
adapter: redis
135136
- gemfile: redis_client
136137
adapter: redis_client
138+
- gemfile: activerecord_trilogy_adapter
139+
adapter: activerecord_trilogy_adapter
137140
services:
138141
mysql:
139142
image: mysql:5.7

.rubocop.yml

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ Lint/SuppressedException:
2828
Minitest/AssertPredicate:
2929
Enabled: false
3030

31+
Minitest/MultipleAssertions:
32+
Enabled: false
33+
3134
Minitest/RefuteFalse:
3235
Enabled: false
3336

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Unreleased
2+
3+
* Support Active Record Trilogy adapter. (#468)
4+
15
# v0.17.0
26

37
* Avoid prepending the same prefix twice to errors messages. (#423)

Gemfile

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ group :test do
1717
# The last stable version for MacOS ARM darwin
1818
gem "grpc", "1.47.0"
1919
gem "mysql2", "~> 0.5"
20-
gem "activerecord", ">= 7.0.3"
20+
gem "trilogy", github: "github/trilogy", branch: "main", glob: "contrib/ruby/*.gemspec"
21+
gem "activerecord", github: "rails/rails", branch: "main"
22+
gem "activerecord-trilogy-adapter", github: "github/activerecord-trilogy-adapter", branch: "main"
2123
gem "hiredis", "~> 0.6"
2224
# NOTE: v0.12.0 required for ruby 3.2.0. https://github.com/redis-rb/redis-client/issues/58
2325
gem "hiredis-client", ">= 0.12.0"

Gemfile.lock

+47-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,7 @@ $ bundle install
859859
[mysql-semian-adapter]: lib/semian/mysql2.rb
860860
[postgres-semian-adapter]: https://github.com/mschoenlaub/semian-postgres
861861
[redis-semian-adapter]: lib/semian/redis.rb
862+
[activerecord-trilogy-semian-adapter]: lib/semian/activerecord_trilogy_adapter.rb
862863
[semian-adapter]: lib/semian/adapter.rb
863864
[nethttp-semian-adapter]: lib/semian/net_http.rb
864865
[nethttp-default-errors]: lib/semian/net_http.rb#L35-L45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
gem "rake"
6+
gem "rake-compiler"
7+
gem "minitest"
8+
gem "mocha"
9+
gem "toxiproxy"
10+
gem "webrick"
11+
12+
gem "trilogy", github: "github/trilogy", branch: "main", glob: "contrib/ruby/*.gemspec"
13+
gem "activerecord-trilogy-adapter", github: "github/activerecord-trilogy-adapter", branch: "main"
14+
gem "activerecord", github: "rails/rails"
15+
16+
gemspec path: "../"

gemfiles/activerecord_trilogy_adapter.gemfile.lock

+74
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# frozen_string_literal: true
2+
3+
require "semian/adapter"
4+
require "activerecord-trilogy-adapter"
5+
require "active_record/connection_adapters/trilogy_adapter"
6+
7+
module ActiveRecord
8+
module ConnectionAdapters
9+
class TrilogyAdapter
10+
ActiveRecord::ActiveRecordError.include(::Semian::AdapterError)
11+
12+
class SemianError < StatementInvalid
13+
def initialize(semian_identifier, *args)
14+
super(*args)
15+
@semian_identifier = semian_identifier
16+
end
17+
end
18+
19+
ResourceBusyError = Class.new(SemianError)
20+
CircuitOpenError = Class.new(SemianError)
21+
end
22+
end
23+
end
24+
25+
module Semian
26+
module ActiveRecordTrilogyAdapter
27+
include Semian::Adapter
28+
29+
ResourceBusyError = ::ActiveRecord::ConnectionAdapters::TrilogyAdapter::ResourceBusyError
30+
CircuitOpenError = ::ActiveRecord::ConnectionAdapters::TrilogyAdapter::CircuitOpenError
31+
32+
attr_reader :raw_semian_options, :semian_identifier
33+
34+
def initialize(*options)
35+
*, config = options
36+
@raw_semian_options = config.delete(:semian)
37+
@semian_identifier = begin
38+
name = semian_options && semian_options[:name]
39+
unless name
40+
host = config[:host] || "localhost"
41+
port = config[:port] || 3306
42+
name = "#{host}:#{port}"
43+
end
44+
:"mysql_#{name}"
45+
end
46+
super
47+
end
48+
49+
def execute(sql, name = nil, async: false, allow_retry: false)
50+
if query_allowlisted?(sql)
51+
super(sql, name, async: async, allow_retry: allow_retry)
52+
else
53+
acquire_semian_resource(adapter: :trilogy_adapter, scope: :execute) do
54+
super(sql, name, async: async, allow_retry: allow_retry)
55+
end
56+
end
57+
end
58+
59+
def active?
60+
acquire_semian_resource(adapter: :trilogy_adapter, scope: :ping) do
61+
super
62+
end
63+
rescue ResourceBusyError, CircuitOpenError
64+
false
65+
end
66+
67+
def with_resource_timeout(temp_timeout)
68+
if connection.nil?
69+
prev_read_timeout = @config[:read_timeout] || 0
70+
@config.merge!(read_timeout: temp_timeout) # Create new client with temp_timeout for read timeout
71+
else
72+
prev_read_timeout = connection.read_timeout
73+
connection.read_timeout = temp_timeout
74+
end
75+
yield
76+
ensure
77+
@config.merge!(read_timeout: prev_read_timeout)
78+
connection&.read_timeout = prev_read_timeout
79+
end
80+
81+
private
82+
83+
def acquire_semian_resource(**)
84+
super
85+
rescue ActiveRecord::StatementInvalid => error
86+
if error.cause.is_a?(Trilogy::TimeoutError)
87+
semian_resource.mark_failed(error)
88+
error.semian_identifier = semian_identifier
89+
end
90+
raise
91+
end
92+
93+
def resource_exceptions
94+
[ActiveRecord::ConnectionNotEstablished]
95+
end
96+
97+
# TODO: share this with Mysql2
98+
QUERY_ALLOWLIST = Regexp.union(
99+
%r{\A(?:/\*.*?\*/)?\s*ROLLBACK}i,
100+
%r{\A(?:/\*.*?\*/)?\s*COMMIT}i,
101+
%r{\A(?:/\*.*?\*/)?\s*RELEASE\s+SAVEPOINT}i,
102+
)
103+
104+
def query_allowlisted?(sql, *)
105+
QUERY_ALLOWLIST.match?(sql)
106+
rescue ArgumentError
107+
return false unless sql.valid_encoding?
108+
109+
raise
110+
end
111+
112+
def connect(*args)
113+
acquire_semian_resource(adapter: :trilogy_adapter, scope: :connection) do
114+
super
115+
end
116+
end
117+
end
118+
end
119+
120+
ActiveRecord::ConnectionAdapters::TrilogyAdapter.prepend(Semian::ActiveRecordTrilogyAdapter)

0 commit comments

Comments
 (0)