Skip to content

Commit

Permalink
Semian + Trilogy: Introduce adapter for Trilogy AR Adapter (#468)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
adrianna-chang-shopify authored and miry committed Feb 8, 2023
1 parent 57d2e0d commit 98d8601
Show file tree
Hide file tree
Showing 10 changed files with 677 additions and 14 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ jobs:
- redis_4
- redis_5
- redis_client
- activerecord_trilogy_adapter
include:
- gemfile: grpc
adapter: grpc
Expand All @@ -134,6 +135,8 @@ jobs:
adapter: redis
- gemfile: redis_client
adapter: redis_client
- gemfile: activerecord_trilogy_adapter
adapter: activerecord_trilogy_adapter
services:
mysql:
image: mysql:5.7
Expand Down
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Lint/SuppressedException:
Minitest/AssertPredicate:
Enabled: false

Minitest/MultipleAssertions:
Enabled: false

Minitest/RefuteFalse:
Enabled: false

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Unreleased

* Support Active Record Trilogy adapter. (#468)

# v0.17.0

* Avoid prepending the same prefix twice to errors messages. (#423)
Expand Down
4 changes: 3 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ group :test do
# The last stable version for MacOS ARM darwin
gem "grpc", "1.47.0"
gem "mysql2", "~> 0.5"
gem "activerecord", ">= 7.0.3"
gem "trilogy", github: "github/trilogy", branch: "main", glob: "contrib/ruby/*.gemspec"
gem "activerecord", github: "rails/rails", branch: "main"
gem "activerecord-trilogy-adapter", github: "github/activerecord-trilogy-adapter", branch: "main"
gem "hiredis", "~> 0.6"
# NOTE: v0.12.0 required for ruby 3.2.0. https://github.com/redis-rb/redis-client/issues/58
gem "hiredis-client", ">= 0.12.0"
Expand Down
60 changes: 47 additions & 13 deletions Gemfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,7 @@ $ bundle install
[mysql-semian-adapter]: lib/semian/mysql2.rb
[postgres-semian-adapter]: https://github.com/mschoenlaub/semian-postgres
[redis-semian-adapter]: lib/semian/redis.rb
[activerecord-trilogy-semian-adapter]: lib/semian/activerecord_trilogy_adapter.rb
[semian-adapter]: lib/semian/adapter.rb
[nethttp-semian-adapter]: lib/semian/net_http.rb
[nethttp-default-errors]: lib/semian/net_http.rb#L35-L45
Expand Down
16 changes: 16 additions & 0 deletions gemfiles/activerecord_trilogy_adapter.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

source "https://rubygems.org"

gem "rake"
gem "rake-compiler"
gem "minitest"
gem "mocha"
gem "toxiproxy"
gem "webrick"

gem "trilogy", github: "github/trilogy", branch: "main", glob: "contrib/ruby/*.gemspec"
gem "activerecord-trilogy-adapter", github: "github/activerecord-trilogy-adapter", branch: "main"
gem "activerecord", github: "rails/rails"

gemspec path: "../"
74 changes: 74 additions & 0 deletions gemfiles/activerecord_trilogy_adapter.gemfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

120 changes: 120 additions & 0 deletions lib/semian/activerecord_trilogy_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# frozen_string_literal: true

require "semian/adapter"
require "activerecord-trilogy-adapter"
require "active_record/connection_adapters/trilogy_adapter"

module ActiveRecord
module ConnectionAdapters
class TrilogyAdapter
ActiveRecord::ActiveRecordError.include(::Semian::AdapterError)

class SemianError < StatementInvalid
def initialize(semian_identifier, *args)
super(*args)
@semian_identifier = semian_identifier
end
end

ResourceBusyError = Class.new(SemianError)
CircuitOpenError = Class.new(SemianError)
end
end
end

module Semian
module ActiveRecordTrilogyAdapter
include Semian::Adapter

ResourceBusyError = ::ActiveRecord::ConnectionAdapters::TrilogyAdapter::ResourceBusyError
CircuitOpenError = ::ActiveRecord::ConnectionAdapters::TrilogyAdapter::CircuitOpenError

attr_reader :raw_semian_options, :semian_identifier

def initialize(*options)
*, config = options
@raw_semian_options = config.delete(:semian)
@semian_identifier = begin
name = semian_options && semian_options[:name]
unless name
host = config[:host] || "localhost"
port = config[:port] || 3306
name = "#{host}:#{port}"
end
:"mysql_#{name}"
end
super
end

def execute(sql, name = nil, async: false, allow_retry: false)
if query_allowlisted?(sql)
super(sql, name, async: async, allow_retry: allow_retry)
else
acquire_semian_resource(adapter: :trilogy_adapter, scope: :execute) do
super(sql, name, async: async, allow_retry: allow_retry)
end
end
end

def active?
acquire_semian_resource(adapter: :trilogy_adapter, scope: :ping) do
super
end
rescue ResourceBusyError, CircuitOpenError
false
end

def with_resource_timeout(temp_timeout)
if connection.nil?
prev_read_timeout = @config[:read_timeout] || 0
@config.merge!(read_timeout: temp_timeout) # Create new client with temp_timeout for read timeout
else
prev_read_timeout = connection.read_timeout
connection.read_timeout = temp_timeout
end
yield
ensure
@config.merge!(read_timeout: prev_read_timeout)
connection&.read_timeout = prev_read_timeout
end

private

def acquire_semian_resource(**)
super
rescue ActiveRecord::StatementInvalid => error
if error.cause.is_a?(Trilogy::TimeoutError)
semian_resource.mark_failed(error)
error.semian_identifier = semian_identifier
end
raise
end

def resource_exceptions
[ActiveRecord::ConnectionNotEstablished]
end

# TODO: share this with Mysql2
QUERY_ALLOWLIST = Regexp.union(
%r{\A(?:/\*.*?\*/)?\s*ROLLBACK}i,
%r{\A(?:/\*.*?\*/)?\s*COMMIT}i,
%r{\A(?:/\*.*?\*/)?\s*RELEASE\s+SAVEPOINT}i,
)

def query_allowlisted?(sql, *)
QUERY_ALLOWLIST.match?(sql)
rescue ArgumentError
return false unless sql.valid_encoding?

raise
end

def connect(*args)
acquire_semian_resource(adapter: :trilogy_adapter, scope: :connection) do
super
end
end
end
end

ActiveRecord::ConnectionAdapters::TrilogyAdapter.prepend(Semian::ActiveRecordTrilogyAdapter)
Loading

0 comments on commit 98d8601

Please sign in to comment.