From ec774fe88e2400598d77f4367f35f120739133fe Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Fri, 30 Aug 2024 17:55:38 +0900 Subject: [PATCH 01/68] Implement integration_test --- .github/workflows/integration_test.yml | 69 ++++++++++++++++++++++++ .gitignore | 2 +- integration_test/Appraisals | 1 + integration_test/Gemfile | 7 +++ integration_test/docker-compose.yml | 23 ++++++++ integration_test/gemfiles/ar_6.1.gemfile | 10 ++++ integration_test/gemfiles/ar_7.0.gemfile | 10 ++++ integration_test/gemfiles/ar_7.1.gemfile | 10 ++++ integration_test/spec/mysql2_spec.rb | 52 ++++++++++++++++++ integration_test/spec/postgresql_spec.rb | 52 ++++++++++++++++++ integration_test/spec/spec_helper.rb | 28 ++++++++++ 11 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/integration_test.yml create mode 120000 integration_test/Appraisals create mode 100644 integration_test/Gemfile create mode 100644 integration_test/docker-compose.yml create mode 100644 integration_test/gemfiles/ar_6.1.gemfile create mode 100644 integration_test/gemfiles/ar_7.0.gemfile create mode 100644 integration_test/gemfiles/ar_7.1.gemfile create mode 100644 integration_test/spec/mysql2_spec.rb create mode 100644 integration_test/spec/postgresql_spec.rb create mode 100644 integration_test/spec/spec_helper.rb diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml new file mode 100644 index 0000000..f44c877 --- /dev/null +++ b/.github/workflows/integration_test.yml @@ -0,0 +1,69 @@ +name: Integration Test + +on: + push: + branches: + - master + pull_request: + branches: + - master + +env: + RUBY_VERSION: 3.3 + +jobs: + mysql: + continue-on-error: true + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./integration_test + env: + BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile + MYSQL_HOST: 127.0.0.1 + strategy: + matrix: + gemfile: + - ar_6.1 + - ar_7.0 + - ar_7.1 + steps: + - uses: actions/checkout@v4 + - name: Start DB + run: docker compose up -d mysql + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ env.RUBY_VERSION }} + - name: Run bundle install + run: bundle install + - name: Run integration test + run: bundle exec rspec spec/mysql2_spec.rb + + postgresql: + continue-on-error: true + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./integration_test + env: + BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile + POSTGRES_HOST: 127.0.0.1 + strategy: + matrix: + gemfile: + - ar_6.1 + - ar_7.0 + - ar_7.1 + steps: + - uses: actions/checkout@v4 + - name: Start DB + run: docker compose up -d postgres + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ env.RUBY_VERSION }} + - name: Run bundle install + run: bundle install + - name: Run integration test + run: bundle exec rspec spec/postgresql_spec.rb diff --git a/.gitignore b/.gitignore index e786654..df4805e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ *.swp *.gem Gemfile.lock -gemfiles/*.lock +*.gemfile.lock .bundle/ diff --git a/integration_test/Appraisals b/integration_test/Appraisals new file mode 120000 index 0000000..526794e --- /dev/null +++ b/integration_test/Appraisals @@ -0,0 +1 @@ +../Appraisals \ No newline at end of file diff --git a/integration_test/Gemfile b/integration_test/Gemfile new file mode 100644 index 0000000..7577434 --- /dev/null +++ b/integration_test/Gemfile @@ -0,0 +1,7 @@ +source 'https://rubygems.org' + +gem 'arproxy', path: '..' +gem 'rspec' +gem 'appraisal' +gem 'mysql2' +gem 'pg' diff --git a/integration_test/docker-compose.yml b/integration_test/docker-compose.yml new file mode 100644 index 0000000..18ccaeb --- /dev/null +++ b/integration_test/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3' + +services: + mysql: + image: mysql:9.0 + restart: always + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: arproxy_test + MYSQL_USER: arproxy + MYSQL_PASSWORD: password + ports: + - "23306:3306" + + postgres: + image: postgres:16 + restart: always + environment: + POSTGRES_DB: arproxy_test + POSTGRES_USER: arproxy + POSTGRES_PASSWORD: password + ports: + - "25432:5432" diff --git a/integration_test/gemfiles/ar_6.1.gemfile b/integration_test/gemfiles/ar_6.1.gemfile new file mode 100644 index 0000000..24d1563 --- /dev/null +++ b/integration_test/gemfiles/ar_6.1.gemfile @@ -0,0 +1,10 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "arproxy", path: "../.." +gem "rspec" +gem "appraisal" +gem "mysql2" +gem "pg" +gem "activerecord", "~> 6.1.0" diff --git a/integration_test/gemfiles/ar_7.0.gemfile b/integration_test/gemfiles/ar_7.0.gemfile new file mode 100644 index 0000000..72e4b83 --- /dev/null +++ b/integration_test/gemfiles/ar_7.0.gemfile @@ -0,0 +1,10 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "arproxy", path: "../.." +gem "rspec" +gem "appraisal" +gem "mysql2" +gem "pg" +gem "activerecord", "~> 7.0.0" diff --git a/integration_test/gemfiles/ar_7.1.gemfile b/integration_test/gemfiles/ar_7.1.gemfile new file mode 100644 index 0000000..5273122 --- /dev/null +++ b/integration_test/gemfiles/ar_7.1.gemfile @@ -0,0 +1,10 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "arproxy", path: "../.." +gem "rspec" +gem "appraisal" +gem "mysql2" +gem "pg" +gem "activerecord", "~> 7.1.0" diff --git a/integration_test/spec/mysql2_spec.rb b/integration_test/spec/mysql2_spec.rb new file mode 100644 index 0000000..d5dca8a --- /dev/null +++ b/integration_test/spec/mysql2_spec.rb @@ -0,0 +1,52 @@ +require_relative 'spec_helper' +require 'mysql2' + +context 'MySQL' do + before(:all) do + ActiveRecord::Base.establish_connection( + adapter: 'mysql2', + host: ENV.fetch('MYSQL_HOST', '127.0.0.1'), + port: ENV.fetch('MYSQL_PORT', '23306').to_i, + database: 'arproxy_test', + username: 'arproxy', + password: 'password' + ) + + Arproxy.configure do |config| + config.adapter = 'mysql2' + config.use HelloProxy + config.use QueryLogger + end + Arproxy.enable! + + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + + Product.create(name: 'apple', price: 100) + Product.create(name: 'banana', price: 200) + Product.create(name: 'orange', price: 300) + end + + after(:all) do + ActiveRecord::Base.connection.drop_table :products + ActiveRecord::Base.connection.close + Arproxy.disable! + end + + before(:each) do + QueryLogger.reset! + end + + it do + expect(QueryLogger.log.size).to eq(0) + + expect(Product.count).to eq(3) + expect(Product.first.name).to eq('apple') + + expect(QueryLogger.log.size).to eq(2) + expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM `products` -- Hello Arproxy!') + expect(QueryLogger.log[1]).to eq('SELECT `products`.* FROM `products` ORDER BY `products`.`id` ASC LIMIT 1 -- Hello Arproxy!') + end +end diff --git a/integration_test/spec/postgresql_spec.rb b/integration_test/spec/postgresql_spec.rb new file mode 100644 index 0000000..737fe39 --- /dev/null +++ b/integration_test/spec/postgresql_spec.rb @@ -0,0 +1,52 @@ +require_relative 'spec_helper' +require 'pg' + +context 'PostgreSQL' do + before(:all) do + ActiveRecord::Base.establish_connection( + adapter: 'postgresql', + host: ENV.fetch('POSTGRES_HOST', '127.0.0.1'), + port: ENV.fetch('POSTGRES_PORT', '25432').to_i, + database: 'arproxy_test', + username: 'arproxy', + password: 'password' + ) + + Arproxy.configure do |config| + config.adapter = 'postgresql' + config.use HelloProxy + config.use QueryLogger + end + Arproxy.enable! + + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + + Product.create(name: 'apple', price: 100) + Product.create(name: 'banana', price: 200) + Product.create(name: 'orange', price: 300) + end + + after(:all) do + ActiveRecord::Base.connection.drop_table :products + ActiveRecord::Base.connection.close + Arproxy.disable! + end + + before(:each) do + QueryLogger.reset! + end + + it do + expect(QueryLogger.log.size).to eq(0) + + expect(Product.count).to eq(3) + expect(Product.first.name).to eq('apple') + + expect(QueryLogger.log.size).to eq(2) + expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM `products` -- Hello Arproxy!') + expect(QueryLogger.log[1]).to eq('SELECT `products`.* FROM `products` ORDER BY `products`.`id` ASC LIMIT 1 -- Hello Arproxy!') + end +end diff --git a/integration_test/spec/spec_helper.rb b/integration_test/spec/spec_helper.rb new file mode 100644 index 0000000..869dec6 --- /dev/null +++ b/integration_test/spec/spec_helper.rb @@ -0,0 +1,28 @@ +require 'arproxy' +require 'active_record' + +class Product < ActiveRecord::Base +end + +class QueryLogger < Arproxy::Base + def execute(sql, name = nil) + @@log ||= [] + @@log << sql + puts "QueryLogger: #{sql}" + super + end + + def self.log + @@log + end + + def self.reset! + @@log = [] + end +end + +class HelloProxy < Arproxy::Base + def execute(sql, name = nil) + super("#{sql} -- Hello Arproxy!", name) + end +end From 6bffb1986f01e1d64d182fba71fcd1318c69fd8d Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 1 Sep 2024 17:40:19 +0900 Subject: [PATCH 02/68] Move dev dependencies into Gemfile --- Gemfile | 3 +++ arproxy.gemspec | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index fa75df1..327e1d0 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,6 @@ source 'https://rubygems.org' gemspec + +gem 'rspec', '~> 3.13' +gem 'appraisal', '~> 2.5' diff --git a/arproxy.gemspec b/arproxy.gemspec index cee2d26..60331bb 100644 --- a/arproxy.gemspec +++ b/arproxy.gemspec @@ -15,8 +15,4 @@ Gem::Specification.new do |spec| spec.add_dependency 'activerecord', '>= 4.2.0' - spec.add_development_dependency "bundler" - spec.add_development_dependency "rake", ">= 12.3.3" - spec.add_development_dependency "rspec", "~> 3.0" - spec.add_development_dependency "appraisal", "~> 2.1" end From aad513e5a899d779039574f645b2e89edcd6dbae Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 1 Sep 2024 17:41:03 +0900 Subject: [PATCH 03/68] Bump AR to 7.1 --- arproxy.gemspec | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arproxy.gemspec b/arproxy.gemspec index 60331bb..16a8a2a 100644 --- a/arproxy.gemspec +++ b/arproxy.gemspec @@ -13,6 +13,5 @@ Gem::Specification.new do |spec| spec.license = "MIT" spec.require_paths = ["lib"] - spec.add_dependency 'activerecord', '>= 4.2.0' - + spec.add_dependency 'activerecord', '~> 7.1' end From 4cfa0becb0221cafc54fc936e1ac25f22abb1a27 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 1 Sep 2024 17:46:43 +0900 Subject: [PATCH 04/68] Refine gemspec --- arproxy.gemspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arproxy.gemspec b/arproxy.gemspec index 16a8a2a..1771ef4 100644 --- a/arproxy.gemspec +++ b/arproxy.gemspec @@ -4,11 +4,11 @@ require 'arproxy/version' Gem::Specification.new do |spec| spec.name = 'arproxy' spec.version = Arproxy::VERSION - spec.summary = 'Proxy between ActiveRecord and DB adapter' - spec.description = 'Arproxy is a proxy between ActiveRecord and database adapter' + spec.summary = 'A proxy layer between ActiveRecord and database adapters' + spec.description = 'Arproxy is a proxy layer that allows hooking into ActiveRecord query execution and injecting custom processing' spec.files = Dir.glob("lib/**/*.rb") spec.author = 'Issei Naruta' - spec.email = 'naruta@cookpad.com' + spec.email = 'mimitako@gmail.com' spec.homepage = 'https://github.com/cookpad/arproxy' spec.license = "MIT" spec.require_paths = ["lib"] From 2fedcb1570d324d6e945fdce34cb2a52087c5be0 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 1 Sep 2024 20:25:14 +0900 Subject: [PATCH 05/68] Introduce rubocop --- .github/workflows/integration_test.yml | 6 +- .github/workflows/rubocop.yml | 30 +++ .github/workflows/ruby.yml | 4 +- .gitignore | 1 + .rubocop.yml | 278 +++++++++++++++++++++++ Gemfile | 6 +- README.md | 8 +- Rakefile | 6 +- arproxy.gemspec | 8 +- gemfiles/ar_4.2.gemfile | 6 +- gemfiles/ar_5.2.gemfile | 6 +- gemfiles/ar_6.1.gemfile | 6 +- gemfiles/ar_7.0.gemfile | 6 +- gemfiles/ar_7.1.gemfile | 6 +- integration_test/gemfiles/ar_6.1.gemfile | 14 +- integration_test/gemfiles/ar_7.0.gemfile | 14 +- integration_test/gemfiles/ar_7.1.gemfile | 14 +- lib/arproxy.rb | 22 +- lib/arproxy/config.rb | 35 ++- lib/arproxy/proxy_chain.rb | 7 +- spec/arproxy/config_spec.rb | 36 +-- spec/arproxy/plugin/test.rb | 2 +- spec/arproxy_spec.rb | 58 ++--- spec/spec_helper.rb | 2 +- 24 files changed, 445 insertions(+), 136 deletions(-) create mode 100644 .github/workflows/rubocop.yml create mode 100644 .rubocop.yml diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index f44c877..42ea1b5 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -3,10 +3,12 @@ name: Integration Test on: push: branches: - - master + - main + - v1 pull_request: branches: - - master + - main + - v1 env: RUBY_VERSION: 3.3 diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 0000000..1ce1639 --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,30 @@ +name: RuboCop + +on: + push: + branches: + - main + - v1 + pull_request: + branches: + - main + - v1 + +jobs: + rubocop: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3 + + - name: Install dependencies + run: bundle install + + - name: Run RuboCop + run: bundle exec rubocop diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 25cb8e4..c0fd01c 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -9,9 +9,9 @@ name: Ruby on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: test: diff --git a/.gitignore b/.gitignore index df4805e..dcb002a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ Gemfile.lock *.gemfile.lock .bundle/ +tmp/ diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..4336825 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,278 @@ +--- +# Referenced from https://github.com/rails/rails/blob/main/.rubocop.yml + +require: + - rubocop-md + +AllCops: + TargetRubyVersion: 3.3 + DisabledByDefault: true + SuggestExtensions: false + Exclude: + - '**/tmp/**/*' + +# Prefer &&/|| over and/or. +Style/AndOr: + Enabled: true + +Layout/ClosingHeredocIndentation: + Enabled: true + +Layout/ClosingParenthesisIndentation: + Enabled: true + +# Align comments with method definitions. +Layout/CommentIndentation: + Enabled: true + +Layout/DefEndAlignment: + Enabled: true + +Layout/ElseAlignment: + Enabled: true + +# Align `end` with the matching keyword or starting expression except for +# assignments, where it should be aligned with the LHS. +Layout/EndAlignment: + Enabled: true + EnforcedStyleAlignWith: variable + AutoCorrect: true + +Layout/EndOfLine: + Enabled: true + +Layout/EmptyLineAfterMagicComment: + Enabled: true + +Layout/EmptyLinesAroundAccessModifier: + Enabled: true + EnforcedStyle: only_before + +Layout/EmptyLinesAroundBlockBody: + Enabled: true + +# In a regular class definition, no empty lines around the body. +Layout/EmptyLinesAroundClassBody: + Enabled: true + +# In a regular method definition, no empty lines around the body. +Layout/EmptyLinesAroundMethodBody: + Enabled: true + +# In a regular module definition, no empty lines around the body. +Layout/EmptyLinesAroundModuleBody: + Enabled: true + +# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. +Style/HashSyntax: + Enabled: true + EnforcedShorthandSyntax: either + +# Method definitions after `private` or `protected` isolated calls need one +# extra level of indentation. +Layout/IndentationConsistency: + Enabled: true + EnforcedStyle: indented_internal_methods + Exclude: + - '**/*.md' + +# Two spaces, no tabs (for indentation). +Layout/IndentationWidth: + Enabled: true + +Layout/LeadingCommentSpace: + Enabled: true + +Layout/SpaceAfterColon: + Enabled: true + +Layout/SpaceAfterComma: + Enabled: true + +Layout/SpaceAfterSemicolon: + Enabled: true + +Layout/SpaceAroundEqualsInParameterDefault: + Enabled: false + +Layout/SpaceAroundKeyword: + Enabled: true + +Layout/SpaceAroundOperators: + Enabled: true + +Layout/SpaceBeforeComma: + Enabled: true + +Layout/SpaceBeforeComment: + Enabled: true + +Layout/SpaceBeforeFirstArg: + Enabled: true + +Style/DefWithParentheses: + Enabled: true + +# Defining a method with parameters needs parentheses. +Style/MethodDefParentheses: + Enabled: true + +Style/ExplicitBlockArgument: + Enabled: true + +Style/MapToHash: + Enabled: true + +Style/RedundantFreeze: + Enabled: true + +# Use `foo {}` not `foo{}`. +Layout/SpaceBeforeBlockBraces: + Enabled: true + +# Use `foo { bar }` not `foo {bar}`. +Layout/SpaceInsideBlockBraces: + Enabled: true + EnforcedStyleForEmptyBraces: space + +# Use `{ a: 1 }` not `{a:1}`. +Layout/SpaceInsideHashLiteralBraces: + Enabled: true + +Layout/SpaceInsideParens: + Enabled: true + +# Check quotes usage according to lint rule below. +Style/StringLiterals: + Enabled: true + +# Detect hard tabs, no hard tabs. +Layout/IndentationStyle: + Enabled: true + +# Empty lines should not have any spaces. +Layout/TrailingEmptyLines: + Enabled: true + +# No trailing whitespace. +Layout/TrailingWhitespace: + Enabled: true + +# Use quotes for string literals when they are enough. +Style/RedundantPercentQ: + Enabled: true + +Lint/AmbiguousOperator: + Enabled: true + +Lint/AmbiguousRegexpLiteral: + Enabled: true + +Lint/Debugger: + Enabled: true + DebuggerRequires: + - debug + +Lint/DuplicateRequire: + Enabled: true + +Lint/DuplicateMagicComment: + Enabled: true + +Lint/DuplicateMethods: + Enabled: true + +Lint/ErbNewArguments: + Enabled: true + +Lint/EnsureReturn: + Enabled: true + +Lint/MissingCopEnableDirective: + Enabled: true + +# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. +Lint/RequireParentheses: + Enabled: true + +Lint/RedundantCopDisableDirective: + Enabled: true + +Lint/RedundantCopEnableDirective: + Enabled: true + +Lint/RedundantRequireStatement: + Enabled: true + +Lint/RedundantStringCoercion: + Enabled: true + +Lint/RedundantSafeNavigation: + Enabled: true + +Lint/UriEscapeUnescape: + Enabled: true + +Lint/UselessAssignment: + Enabled: true + +Lint/DeprecatedClassMethods: + Enabled: true + +Lint/InterpolationCheck: + Enabled: true + +Lint/SafeNavigationChain: + Enabled: true + +Style/EvalWithLocation: + Enabled: false + +Style/ParenthesesAroundCondition: + Enabled: true + +Style/HashTransformKeys: + Enabled: true + +Style/HashTransformValues: + Enabled: true + +Style/RedundantBegin: + Enabled: true + +Style/RedundantReturn: + Enabled: true + AllowMultipleReturnValues: true + +Style/RedundantRegexpEscape: + Enabled: true + +Style/Semicolon: + Enabled: true + AllowAsExpressionSeparator: true + +# Prefer Foo.method over Foo::method +Style/ColonMethodCall: + Enabled: true + +Style/TrivialAccessors: + Enabled: true + +# Prefer a = b || c over a = b ? b : c +Style/RedundantCondition: + Enabled: true + +Style/RedundantDoubleSplatHashBraces: + Enabled: true + +Style/OpenStructUse: + Enabled: true + +Style/ArrayIntersect: + Enabled: true + +Markdown: + # Whether to run RuboCop against non-valid snippets + WarnInvalid: true + # Whether to lint codeblocks without code attributes + Autodetect: false diff --git a/Gemfile b/Gemfile index 327e1d0..0031a7f 100644 --- a/Gemfile +++ b/Gemfile @@ -2,5 +2,7 @@ source 'https://rubygems.org' gemspec -gem 'rspec', '~> 3.13' -gem 'appraisal', '~> 2.5' +gem 'rspec' +gem 'appraisal' +gem 'rubocop' +gem 'rubocop-md' diff --git a/README.md b/README.md index 605d668..231fb27 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ class QueryTracer < Arproxy::Base end Arproxy.configure do |config| - config.adapter = "mysql2" # A DB Apdapter name which is used in your database.yml + config.adapter = 'mysql2' # A DB Apdapter name which is used in your database.yml config.use QueryTracer end Arproxy.enable! @@ -27,7 +27,7 @@ Then you can see the backtrace of SQLs in the Rails' log. ```ruby # In your Rails code -MyTable.where(:id => id).limit(1) # => The SQL and the backtrace appear in the log +MyTable.where(id: id).limit(1) # => The SQL and the backtrace appear in the log ``` ## Architecture @@ -43,7 +43,7 @@ With Arproxy: ```ruby Arproxy.configure do |config| - config.adapter = "mysql2" + config.adapter = 'mysql2' config.use MyProxy1 config.use MyProxy2 end @@ -82,7 +82,7 @@ end ```ruby class CommentAdder < Arproxy::Base def execute(sql, name=nil) - sql += " /*this_is_comment*/" + sql += ' /*this_is_comment*/' super(sql, name) end end diff --git a/Rakefile b/Rakefile index b7e9ed5..4c774a2 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ -require "bundler/gem_tasks" -require "rspec/core/rake_task" +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) -task :default => :spec +task default: :spec diff --git a/arproxy.gemspec b/arproxy.gemspec index 1771ef4..69cd03e 100644 --- a/arproxy.gemspec +++ b/arproxy.gemspec @@ -1,4 +1,4 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('../lib', __FILE__) require 'arproxy/version' Gem::Specification.new do |spec| @@ -6,12 +6,12 @@ Gem::Specification.new do |spec| spec.version = Arproxy::VERSION spec.summary = 'A proxy layer between ActiveRecord and database adapters' spec.description = 'Arproxy is a proxy layer that allows hooking into ActiveRecord query execution and injecting custom processing' - spec.files = Dir.glob("lib/**/*.rb") + spec.files = Dir.glob('lib/**/*.rb') spec.author = 'Issei Naruta' spec.email = 'mimitako@gmail.com' spec.homepage = 'https://github.com/cookpad/arproxy' - spec.license = "MIT" - spec.require_paths = ["lib"] + spec.license = 'MIT' + spec.require_paths = ['lib'] spec.add_dependency 'activerecord', '~> 7.1' end diff --git a/gemfiles/ar_4.2.gemfile b/gemfiles/ar_4.2.gemfile index 9be8642..37f20bc 100644 --- a/gemfiles/ar_4.2.gemfile +++ b/gemfiles/ar_4.2.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "activerecord", "~> 4.2.0" +gem 'activerecord', '~> 4.2.0' -gemspec path: "../" +gemspec path: '../' diff --git a/gemfiles/ar_5.2.gemfile b/gemfiles/ar_5.2.gemfile index 027888d..aaef0a9 100644 --- a/gemfiles/ar_5.2.gemfile +++ b/gemfiles/ar_5.2.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "activerecord", "~> 5.2.0" +gem 'activerecord', '~> 5.2.0' -gemspec path: "../" +gemspec path: '../' diff --git a/gemfiles/ar_6.1.gemfile b/gemfiles/ar_6.1.gemfile index 07548db..242d68a 100644 --- a/gemfiles/ar_6.1.gemfile +++ b/gemfiles/ar_6.1.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "activerecord", "~> 6.1.0" +gem 'activerecord', '~> 6.1.0' -gemspec path: "../" +gemspec path: '../' diff --git a/gemfiles/ar_7.0.gemfile b/gemfiles/ar_7.0.gemfile index bc1dfc9..73168da 100644 --- a/gemfiles/ar_7.0.gemfile +++ b/gemfiles/ar_7.0.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "activerecord", "~> 7.0.0" +gem 'activerecord', '~> 7.0.0' -gemspec path: "../" +gemspec path: '../' diff --git a/gemfiles/ar_7.1.gemfile b/gemfiles/ar_7.1.gemfile index 69bc38a..3285705 100644 --- a/gemfiles/ar_7.1.gemfile +++ b/gemfiles/ar_7.1.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "activerecord", "~> 7.1.0" +gem 'activerecord', '~> 7.1.0' -gemspec path: "../" +gemspec path: '../' diff --git a/integration_test/gemfiles/ar_6.1.gemfile b/integration_test/gemfiles/ar_6.1.gemfile index 24d1563..4f95002 100644 --- a/integration_test/gemfiles/ar_6.1.gemfile +++ b/integration_test/gemfiles/ar_6.1.gemfile @@ -1,10 +1,10 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "arproxy", path: "../.." -gem "rspec" -gem "appraisal" -gem "mysql2" -gem "pg" -gem "activerecord", "~> 6.1.0" +gem 'arproxy', path: '../..' +gem 'rspec' +gem 'appraisal' +gem 'mysql2' +gem 'pg' +gem 'activerecord', '~> 6.1.0' diff --git a/integration_test/gemfiles/ar_7.0.gemfile b/integration_test/gemfiles/ar_7.0.gemfile index 72e4b83..d78c2bb 100644 --- a/integration_test/gemfiles/ar_7.0.gemfile +++ b/integration_test/gemfiles/ar_7.0.gemfile @@ -1,10 +1,10 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "arproxy", path: "../.." -gem "rspec" -gem "appraisal" -gem "mysql2" -gem "pg" -gem "activerecord", "~> 7.0.0" +gem 'arproxy', path: '../..' +gem 'rspec' +gem 'appraisal' +gem 'mysql2' +gem 'pg' +gem 'activerecord', '~> 7.0.0' diff --git a/integration_test/gemfiles/ar_7.1.gemfile b/integration_test/gemfiles/ar_7.1.gemfile index 5273122..d564dbb 100644 --- a/integration_test/gemfiles/ar_7.1.gemfile +++ b/integration_test/gemfiles/ar_7.1.gemfile @@ -1,10 +1,10 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "arproxy", path: "../.." -gem "rspec" -gem "appraisal" -gem "mysql2" -gem "pg" -gem "activerecord", "~> 7.1.0" +gem 'arproxy', path: '../..' +gem 'rspec' +gem 'appraisal' +gem 'mysql2' +gem 'pg' +gem 'activerecord', '~> 7.1.0' diff --git a/lib/arproxy.rb b/lib/arproxy.rb index 3992263..27a1d06 100644 --- a/lib/arproxy.rb +++ b/lib/arproxy.rb @@ -1,9 +1,9 @@ -require "logger" -require "arproxy/base" -require "arproxy/config" -require "arproxy/proxy_chain" -require "arproxy/error" -require "arproxy/plugin" +require 'logger' +require 'arproxy/base' +require 'arproxy/config' +require 'arproxy/proxy_chain' +require 'arproxy/error' +require 'arproxy/plugin' module Arproxy @config = @enabled = nil @@ -20,12 +20,12 @@ def configure def enable! if enable? - Arproxy.logger.warn "Arproxy has been already enabled" + Arproxy.logger.warn 'Arproxy has been already enabled' return end unless @config - raise Arproxy::Error, "Arproxy should be configured" + raise Arproxy::Error, 'Arproxy should be configured' end @proxy_chain = ProxyChain.new @config @@ -36,7 +36,7 @@ def enable! def disable! unless enable? - Arproxy.logger.warn "Arproxy is not enabled yet" + Arproxy.logger.warn 'Arproxy is not enabled yet' return end @@ -60,11 +60,9 @@ def reenable! end def logger - @logger ||= begin - @config && @config.logger || + @logger ||= @config && @config.logger || defined?(::Rails) && ::Rails.logger || ::Logger.new(STDOUT) - end end def proxy_chain diff --git a/lib/arproxy/config.rb b/lib/arproxy/config.rb index 379dc7e..28648e4 100644 --- a/lib/arproxy/config.rb +++ b/lib/arproxy/config.rb @@ -1,5 +1,5 @@ -require "active_record" -require "active_record/base" +require 'active_record' +require 'active_record/base' module Arproxy class Config @@ -9,7 +9,7 @@ class Config def initialize @proxies = [] if defined?(Rails) - @adapter = Rails.application.config_for(:database)["adapter"] + @adapter = Rails.application.config_for(:database)['adapter'] end end @@ -24,7 +24,7 @@ def plugin(name, *options) end def adapter_class - raise Arproxy::Error, "config.adapter must be set" unless @adapter + raise Arproxy::Error, 'config.adapter must be set' unless @adapter case @adapter when String, Symbol eval "::ActiveRecord::ConnectionAdapters::#{camelized_adapter_name}Adapter" @@ -36,20 +36,19 @@ def adapter_class end private - - def camelized_adapter_name - adapter_name = @adapter.to_s.split("_").map(&:capitalize).join - - case adapter_name - when 'Sqlite3' - 'SQLite3' - when 'Sqlserver' - 'SQLServer' - when 'Postgresql' - 'PostgreSQL' - else - adapter_name + def camelized_adapter_name + adapter_name = @adapter.to_s.split('_').map(&:capitalize).join + + case adapter_name + when 'Sqlite3' + 'SQLite3' + when 'Sqlserver' + 'SQLServer' + when 'Postgresql' + 'PostgreSQL' + else + adapter_name + end end - end end end diff --git a/lib/arproxy/proxy_chain.rb b/lib/arproxy/proxy_chain.rb index e884795..28b97f9 100644 --- a/lib/arproxy/proxy_chain.rb +++ b/lib/arproxy/proxy_chain.rb @@ -1,5 +1,5 @@ module Arproxy - autoload :ChainTail, "arproxy/chain_tail" + autoload :ChainTail, 'arproxy/chain_tail' class ProxyChain attr_reader :head, :tail @@ -35,14 +35,14 @@ def execute_with_arproxy(sql, name=nil, **kwargs) end alias_method :execute_without_arproxy, :execute alias_method :execute, :execute_with_arproxy - ::Arproxy.logger.debug("Arproxy: Enabled") + ::Arproxy.logger.debug('Arproxy: Enabled') end end def disable! @config.adapter_class.class_eval do alias_method :execute, :execute_without_arproxy - ::Arproxy.logger.debug("Arproxy: Disabled") + ::Arproxy.logger.debug('Arproxy: Disabled') end end @@ -53,6 +53,5 @@ def connection def connection=(val) Thread.current[:arproxy_connection] = val end - end end diff --git a/spec/arproxy/config_spec.rb b/spec/arproxy/config_spec.rb index ec3231b..c4adbcc 100644 --- a/spec/arproxy/config_spec.rb +++ b/spec/arproxy/config_spec.rb @@ -1,26 +1,26 @@ -require "spec_helper" +require 'spec_helper' describe Arproxy::Config do - describe "#adapter default value" do + describe '#adapter default value' do subject { Arproxy::Config.new.adapter } - context "when Rails is defined" do + context 'when Rails is defined' do let(:rails) { Module.new } around do |example| - Object.const_set("Rails", rails) + Object.const_set('Rails', rails) example.run - Object.send(:remove_const, "Rails") + Object.send(:remove_const, 'Rails') end before do - allow(rails).to receive_message_chain("application.config_for") { database_config } + allow(rails).to receive_message_chain('application.config_for') { database_config } end - context "when adapter is configured in database.yml" do - let(:database_config) { { "adapter" => "mysql2" } } + context 'when adapter is configured in database.yml' do + let(:database_config) { { 'adapter' => 'mysql2' } } - it { should == "mysql2" } + it { should == 'mysql2' } end context "when adapter isn't configured in database.yml" do @@ -35,7 +35,7 @@ end end - describe "#adapter_class" do + describe '#adapter_class' do subject { config.adapter_class } let(:config) { Arproxy::Config.new } @@ -44,44 +44,44 @@ end context "when adapter is configured as 'mysql2'" do - let(:adapter) { "mysql2" } + let(:adapter) { 'mysql2' } let(:mysql2_class) { Class.new } before do - stub_const("ActiveRecord::ConnectionAdapters::Mysql2Adapter", mysql2_class) + stub_const('ActiveRecord::ConnectionAdapters::Mysql2Adapter', mysql2_class) end it { should == mysql2_class } end context "when adapter is configured as 'sqlite3'" do - let(:adapter) { "sqlite3" } + let(:adapter) { 'sqlite3' } let(:sqlite3_class) { Class.new } before do - stub_const("ActiveRecord::ConnectionAdapters::SQLite3Adapter", sqlite3_class) + stub_const('ActiveRecord::ConnectionAdapters::SQLite3Adapter', sqlite3_class) end it { should == sqlite3_class } end context "when adapter is configured as 'sqlserver'" do - let(:adapter) { "sqlserver" } + let(:adapter) { 'sqlserver' } let(:sqlserver_class) { Class.new } before do - stub_const("ActiveRecord::ConnectionAdapters::SQLServerAdapter", sqlserver_class) + stub_const('ActiveRecord::ConnectionAdapters::SQLServerAdapter', sqlserver_class) end it { should == sqlserver_class } end context "when adapter is configured as 'postgresql'" do - let(:adapter) { "postgresql" } + let(:adapter) { 'postgresql' } let(:postgresql_class) { Class.new } before do - stub_const("ActiveRecord::ConnectionAdapters::PostgreSQLAdapter", postgresql_class) + stub_const('ActiveRecord::ConnectionAdapters::PostgreSQLAdapter', postgresql_class) end it { should == postgresql_class } diff --git a/spec/arproxy/plugin/test.rb b/spec/arproxy/plugin/test.rb index 0af0eb7..f066faf 100644 --- a/spec/arproxy/plugin/test.rb +++ b/spec/arproxy/plugin/test.rb @@ -7,7 +7,7 @@ def initialize(*options) end def execute(sql, name=nil) - {:sql => "#{sql}_PLUGIN", :name => "#{name}_PLUGIN", :options => @options} + { sql: "#{sql}_PLUGIN", name: "#{name}_PLUGIN", options: @options } end end end diff --git a/spec/arproxy_spec.rb b/spec/arproxy_spec.rb index 22d52ce..8dedb13 100644 --- a/spec/arproxy_spec.rb +++ b/spec/arproxy_spec.rb @@ -1,4 +1,4 @@ -require "spec_helper" +require 'spec_helper' describe Arproxy do before do @@ -25,127 +25,127 @@ module ::ActiveRecord module ConnectionAdapters class DummyAdapter def execute(sql, name = nil) - {:sql => sql, :name => name} + { sql: sql, name: name } end end end end let(:connection) { ::ActiveRecord::ConnectionAdapters::DummyAdapter.new } - subject { connection.execute "SQL", "NAME" } + subject { connection.execute 'SQL', 'NAME' } after(:each) do Arproxy.disable! end - context "with a proxy" do + context 'with a proxy' do before do Arproxy.clear_configuration Arproxy.configure do |config| - config.adapter = "dummy" + config.adapter = 'dummy' config.use ProxyA end Arproxy.enable! end - it { should == {:sql => "SQL_A", :name => "NAME_A"} } + it { should == { sql: 'SQL_A', name: 'NAME_A' } } end - context "with 2 proxies" do + context 'with 2 proxies' do before do Arproxy.clear_configuration Arproxy.configure do |config| - config.adapter = "dummy" + config.adapter = 'dummy' config.use ProxyA config.use ProxyB end Arproxy.enable! end - it { should == {:sql => "SQL_A_B", :name => "NAME_A_B"} } + it { should == { sql: 'SQL_A_B', name: 'NAME_A_B' } } end - context "with 2 proxies which have an option" do + context 'with 2 proxies which have an option' do before do Arproxy.clear_configuration Arproxy.configure do |config| - config.adapter = "dummy" + config.adapter = 'dummy' config.use ProxyA config.use ProxyB, 1 end Arproxy.enable! end - it { should == {:sql => "SQL_A_B1", :name => "NAME_A_B1"} } + it { should == { sql: 'SQL_A_B1', name: 'NAME_A_B1' } } end context do before do Arproxy.clear_configuration Arproxy.configure do |config| - config.adapter = "dummy" + config.adapter = 'dummy' config.use ProxyA end end - context "enable -> disable" do + context 'enable -> disable' do before do Arproxy.enable! Arproxy.disable! end - it { should == {:sql => "SQL", :name => "NAME"} } + it { should == { sql: 'SQL', name: 'NAME' } } end - context "enable -> enable" do + context 'enable -> enable' do before do Arproxy.enable! Arproxy.enable! end - it { should == {:sql => "SQL_A", :name => "NAME_A"} } + it { should == { sql: 'SQL_A', name: 'NAME_A' } } end - context "enable -> disable -> disable" do + context 'enable -> disable -> disable' do before do Arproxy.enable! Arproxy.disable! Arproxy.disable! end - it { should == {:sql => "SQL", :name => "NAME"} } + it { should == { sql: 'SQL', name: 'NAME' } } end - context "enable -> disable -> enable" do + context 'enable -> disable -> enable' do before do Arproxy.enable! Arproxy.disable! Arproxy.enable! end - it { should == {:sql => "SQL_A", :name => "NAME_A"} } + it { should == { sql: 'SQL_A', name: 'NAME_A' } } end - context "re-configure" do + context 're-configure' do before do Arproxy.configure do |config| config.use ProxyB end Arproxy.enable! end - it { should == {:sql => "SQL_A_B", :name => "NAME_A_B"} } + it { should == { sql: 'SQL_A_B', name: 'NAME_A_B' } } end end - context "use a plug-in" do + context 'use a plug-in' do before do Arproxy.clear_configuration Arproxy.configure do |config| - config.adapter = "dummy" + config.adapter = 'dummy' config.plugin :test, :option_a, :option_b end Arproxy.enable! end - it { should == {:sql => "SQL_PLUGIN", :name => "NAME_PLUGIN", :options => [:option_a, :option_b]} } + it { should == { sql: 'SQL_PLUGIN', name: 'NAME_PLUGIN', options: [:option_a, :option_b] } } end - context "ProxyChain thread-safety" do + context 'ProxyChain thread-safety' do class ProxyWithConnectionId < Arproxy::Base def execute(sql, name) sleep 0.1 @@ -156,13 +156,13 @@ def execute(sql, name) before do Arproxy.clear_configuration Arproxy.configure do |config| - config.adapter = "dummy" + config.adapter = 'dummy' config.use ProxyWithConnectionId end Arproxy.enable! end - context "with two threads" do + context 'with two threads' do let!(:thr1) { Thread.new { connection.dup.execute 'SELECT 1' } } let!(:thr2) { Thread.new { connection.dup.execute 'SELECT 1' } } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 686e97f..6727b44 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1 +1 @@ -require File.expand_path("../../lib/arproxy", __FILE__) +require File.expand_path('../../lib/arproxy', __FILE__) From 65424e6dc660c28bba46d3f02630a7d0f6201962 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 1 Sep 2024 20:43:37 +0900 Subject: [PATCH 06/68] Add rubocop rake task --- Rakefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 4c774a2..cf9c317 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,9 @@ require 'bundler/gem_tasks' require 'rspec/core/rake_task' +require 'rubocop/rake_task' RSpec::Core::RakeTask.new(:spec) -task default: :spec +RuboCop::RakeTask.new(:rubocop) + +task default: [:spec, :rubocop] From 50def5f11bad96fdc55b599374cb29aaaf87f552 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 1 Sep 2024 20:49:16 +0900 Subject: [PATCH 07/68] Change gemfiles to AR 7.1+ --- .github/workflows/integration_test.yml | 6 ++---- .github/workflows/ruby.yml | 5 ++--- .rubocop.yml | 1 + Appraisals | 12 ++++-------- gemfiles/ar_7.1.gemfile | 10 +++++++--- gemfiles/ar_7.2.gemfile | 11 +++++++++++ integration_test/gemfiles/ar_7.1.gemfile | 14 +++++++------- integration_test/gemfiles/ar_7.2.gemfile | 10 ++++++++++ 8 files changed, 44 insertions(+), 25 deletions(-) create mode 100644 gemfiles/ar_7.2.gemfile create mode 100644 integration_test/gemfiles/ar_7.2.gemfile diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 42ea1b5..f1caa13 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -26,9 +26,8 @@ jobs: strategy: matrix: gemfile: - - ar_6.1 - - ar_7.0 - ar_7.1 + - ar_7.2 steps: - uses: actions/checkout@v4 - name: Start DB @@ -54,9 +53,8 @@ jobs: strategy: matrix: gemfile: - - ar_6.1 - - ar_7.0 - ar_7.1 + - ar_7.2 steps: - uses: actions/checkout@v4 - name: Start DB diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index c0fd01c..5f06868 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -19,8 +19,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3'] - gemfile: ['ar_6.1', 'ar_7.0', 'ar_7.1'] + ruby-version: ['3.2', '3.3'] + gemfile: ['ar_7.1', 'ar_7.2'] steps: - uses: actions/checkout@v2 @@ -35,4 +35,3 @@ jobs: run: bundle install && bundle exec rake spec env: BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile - diff --git a/.rubocop.yml b/.rubocop.yml index 4336825..e88625d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -10,6 +10,7 @@ AllCops: SuggestExtensions: false Exclude: - '**/tmp/**/*' + - '**/*.gemfile' # Prefer &&/|| over and/or. Style/AndOr: diff --git a/Appraisals b/Appraisals index d50f53b..2f707bb 100644 --- a/Appraisals +++ b/Appraisals @@ -1,11 +1,7 @@ -appraise 'ar-6.1' do - gem 'activerecord', '~> 6.1.0' -end - -appraise 'ar-7.0' do - gem 'activerecord', '~> 7.0.0' -end - appraise 'ar-7.1' do gem 'activerecord', '~> 7.1.0' end + +appraise 'ar-7.2' do + gem 'activerecord', '~> 7.2.0' +end diff --git a/gemfiles/ar_7.1.gemfile b/gemfiles/ar_7.1.gemfile index 3285705..c325fd4 100644 --- a/gemfiles/ar_7.1.gemfile +++ b/gemfiles/ar_7.1.gemfile @@ -1,7 +1,11 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'activerecord', '~> 7.1.0' +gem "rspec" +gem "appraisal" +gem "rubocop" +gem "rubocop-md" +gem "activerecord", "~> 7.1.0" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/ar_7.2.gemfile b/gemfiles/ar_7.2.gemfile new file mode 100644 index 0000000..4020211 --- /dev/null +++ b/gemfiles/ar_7.2.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rspec" +gem "appraisal" +gem "rubocop" +gem "rubocop-md" +gem "activerecord", "~> 7.2.0" + +gemspec path: "../" diff --git a/integration_test/gemfiles/ar_7.1.gemfile b/integration_test/gemfiles/ar_7.1.gemfile index d564dbb..5273122 100644 --- a/integration_test/gemfiles/ar_7.1.gemfile +++ b/integration_test/gemfiles/ar_7.1.gemfile @@ -1,10 +1,10 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'arproxy', path: '../..' -gem 'rspec' -gem 'appraisal' -gem 'mysql2' -gem 'pg' -gem 'activerecord', '~> 7.1.0' +gem "arproxy", path: "../.." +gem "rspec" +gem "appraisal" +gem "mysql2" +gem "pg" +gem "activerecord", "~> 7.1.0" diff --git a/integration_test/gemfiles/ar_7.2.gemfile b/integration_test/gemfiles/ar_7.2.gemfile new file mode 100644 index 0000000..39617ad --- /dev/null +++ b/integration_test/gemfiles/ar_7.2.gemfile @@ -0,0 +1,10 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "arproxy", path: "../.." +gem "rspec" +gem "appraisal" +gem "mysql2" +gem "pg" +gem "activerecord", "~> 7.2.0" From 6ea73a044fe56b6f0276a2755b6d0221af8d3a98 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 8 Sep 2024 14:05:11 +0900 Subject: [PATCH 08/68] AR7.1: call raw_execute() instead of execute() --- integration_test/spec/spec_helper.rb | 6 +++--- lib/arproxy/chain_tail.rb | 2 +- lib/arproxy/proxy_chain.rb | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/integration_test/spec/spec_helper.rb b/integration_test/spec/spec_helper.rb index 869dec6..45825f8 100644 --- a/integration_test/spec/spec_helper.rb +++ b/integration_test/spec/spec_helper.rb @@ -5,7 +5,7 @@ class Product < ActiveRecord::Base end class QueryLogger < Arproxy::Base - def execute(sql, name = nil) + def execute(sql, name = nil, **kwargs) @@log ||= [] @@log << sql puts "QueryLogger: #{sql}" @@ -22,7 +22,7 @@ def self.reset! end class HelloProxy < Arproxy::Base - def execute(sql, name = nil) - super("#{sql} -- Hello Arproxy!", name) + def execute(sql, name = nil, **kwargs) + super("#{sql} -- Hello Arproxy!", name, **kwargs) end end diff --git a/lib/arproxy/chain_tail.rb b/lib/arproxy/chain_tail.rb index cf35f83..2c82759 100644 --- a/lib/arproxy/chain_tail.rb +++ b/lib/arproxy/chain_tail.rb @@ -5,7 +5,7 @@ def initialize(proxy_chain) end def execute(sql, name=nil, **kwargs) - self.proxy_chain.connection.execute_without_arproxy sql, name, **kwargs + self.proxy_chain.connection.send(:raw_execute_without_arproxy, sql, name, **kwargs) end end end diff --git a/lib/arproxy/proxy_chain.rb b/lib/arproxy/proxy_chain.rb index 28b97f9..b32b78d 100644 --- a/lib/arproxy/proxy_chain.rb +++ b/lib/arproxy/proxy_chain.rb @@ -29,19 +29,19 @@ def reenable! def enable! @config.adapter_class.class_eval do - def execute_with_arproxy(sql, name=nil, **kwargs) + def raw_execute_with_arproxy(sql, name=nil, **kwargs) ::Arproxy.proxy_chain.connection = self ::Arproxy.proxy_chain.head.execute sql, name, **kwargs end - alias_method :execute_without_arproxy, :execute - alias_method :execute, :execute_with_arproxy + alias_method :raw_execute_without_arproxy, :raw_execute + alias_method :raw_execute, :raw_execute_with_arproxy ::Arproxy.logger.debug('Arproxy: Enabled') end end def disable! @config.adapter_class.class_eval do - alias_method :execute, :execute_without_arproxy + alias_method :raw_execute, :raw_execute_without_arproxy ::Arproxy.logger.debug('Arproxy: Disabled') end end From 6534098d93418e70fec65c2a947345ca5ec58ec8 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 8 Sep 2024 20:27:08 +0900 Subject: [PATCH 09/68] don't pass kwargs to proxies --- integration_test/spec/spec_helper.rb | 8 ++++---- lib/arproxy/base.rb | 4 ++-- lib/arproxy/chain_tail.rb | 4 ++-- lib/arproxy/proxy_chain.rb | 3 ++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/integration_test/spec/spec_helper.rb b/integration_test/spec/spec_helper.rb index 45825f8..30ddfef 100644 --- a/integration_test/spec/spec_helper.rb +++ b/integration_test/spec/spec_helper.rb @@ -5,10 +5,10 @@ class Product < ActiveRecord::Base end class QueryLogger < Arproxy::Base - def execute(sql, name = nil, **kwargs) + def execute(sql, name = nil) @@log ||= [] @@log << sql - puts "QueryLogger: #{sql}" + puts "QueryLogger: [#{name}] #{sql}" super end @@ -22,7 +22,7 @@ def self.reset! end class HelloProxy < Arproxy::Base - def execute(sql, name = nil, **kwargs) - super("#{sql} -- Hello Arproxy!", name, **kwargs) + def execute(sql, name = nil) + super("#{sql} -- Hello Arproxy!", name) end end diff --git a/lib/arproxy/base.rb b/lib/arproxy/base.rb index 96d2807..d9336e1 100644 --- a/lib/arproxy/base.rb +++ b/lib/arproxy/base.rb @@ -2,8 +2,8 @@ module Arproxy class Base attr_accessor :proxy_chain, :next_proxy - def execute(sql, name=nil, **kwargs) - next_proxy.execute sql, name, **kwargs + def execute(sql, name=nil) + next_proxy.execute sql, name end end end diff --git a/lib/arproxy/chain_tail.rb b/lib/arproxy/chain_tail.rb index 2c82759..593ff5a 100644 --- a/lib/arproxy/chain_tail.rb +++ b/lib/arproxy/chain_tail.rb @@ -4,8 +4,8 @@ def initialize(proxy_chain) self.proxy_chain = proxy_chain end - def execute(sql, name=nil, **kwargs) - self.proxy_chain.connection.send(:raw_execute_without_arproxy, sql, name, **kwargs) + def execute(sql, name=nil) + [sql, name] end end end diff --git a/lib/arproxy/proxy_chain.rb b/lib/arproxy/proxy_chain.rb index b32b78d..6c64623 100644 --- a/lib/arproxy/proxy_chain.rb +++ b/lib/arproxy/proxy_chain.rb @@ -31,7 +31,8 @@ def enable! @config.adapter_class.class_eval do def raw_execute_with_arproxy(sql, name=nil, **kwargs) ::Arproxy.proxy_chain.connection = self - ::Arproxy.proxy_chain.head.execute sql, name, **kwargs + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) end alias_method :raw_execute_without_arproxy, :raw_execute alias_method :raw_execute, :raw_execute_with_arproxy From 46cd60b38881d1077d12ef38b5a31154b92d9589 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 8 Sep 2024 21:03:20 +0900 Subject: [PATCH 10/68] Patch classes --- .../connection_adapter_patches/base_patch.rb | 19 +++++++++++++ .../mysql2_patch.rb | 27 +++++++++++++++++++ .../patch_factory.rb | 21 +++++++++++++++ lib/arproxy/proxy_chain.rb | 22 +++++---------- 4 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 lib/arproxy/connection_adapter_patches/base_patch.rb create mode 100644 lib/arproxy/connection_adapter_patches/mysql2_patch.rb create mode 100644 lib/arproxy/connection_adapter_patches/patch_factory.rb diff --git a/lib/arproxy/connection_adapter_patches/base_patch.rb b/lib/arproxy/connection_adapter_patches/base_patch.rb new file mode 100644 index 0000000..8e5cd13 --- /dev/null +++ b/lib/arproxy/connection_adapter_patches/base_patch.rb @@ -0,0 +1,19 @@ +module Arproxy + module ConnectionAdapterPatches + class BasePatch + attr_reader :adapter_class + + def initialize(adapter_class) + @adapter_class = adapter_class + end + + def enable! + raise NotImplementedError + end + + def disable! + raise NotImplementedError + end + end + end +end diff --git a/lib/arproxy/connection_adapter_patches/mysql2_patch.rb b/lib/arproxy/connection_adapter_patches/mysql2_patch.rb new file mode 100644 index 0000000..54423e8 --- /dev/null +++ b/lib/arproxy/connection_adapter_patches/mysql2_patch.rb @@ -0,0 +1,27 @@ +require 'arproxy/connection_adapter_patches/base_patch' + +module Arproxy + module ConnectionAdapterPatches + class Mysql2Patch < BasePatch + def enable! + adapter_class.class_eval do + def raw_execute_with_arproxy(sql, name=nil, **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) + end + alias_method :raw_execute_without_arproxy, :raw_execute + alias_method :raw_execute, :raw_execute_with_arproxy + ::Arproxy.logger.debug('Arproxy: Enabled') + end + end + + def disable! + adapter_class.class_eval do + alias_method :raw_execute, :raw_execute_without_arproxy + ::Arproxy.logger.debug('Arproxy: Disabled') + end + end + end + end +end diff --git a/lib/arproxy/connection_adapter_patches/patch_factory.rb b/lib/arproxy/connection_adapter_patches/patch_factory.rb new file mode 100644 index 0000000..894cf8c --- /dev/null +++ b/lib/arproxy/connection_adapter_patches/patch_factory.rb @@ -0,0 +1,21 @@ +module Arproxy + module ConnectionAdapterPatches + SUPPORTED_ADAPTERS = %w[mysql2 postgresql] + class PatchFactory + def self.create(adapter_class) + patch_class(adapter_class).new(adapter_class) + end + + private + def self.patch_class(adapter_class) + adapter_name = adapter_class::ADAPTER_NAME.downcase + if SUPPORTED_ADAPTERS.include?(adapter_name.to_s) + require "arproxy/connection_adapter_patches/#{adapter_name}_patch" + "Arproxy::ConnectionAdapterPatches::#{adapter_name.to_s.camelize}Patch".constantize + else + raise ArgumentError, "Unsupported adapter: #{adapter_name.inspect}" + end + end + end + end +end diff --git a/lib/arproxy/proxy_chain.rb b/lib/arproxy/proxy_chain.rb index 6c64623..3297223 100644 --- a/lib/arproxy/proxy_chain.rb +++ b/lib/arproxy/proxy_chain.rb @@ -1,6 +1,7 @@ -module Arproxy - autoload :ChainTail, 'arproxy/chain_tail' +require_relative './chain_tail' +require_relative './connection_adapter_patches/patch_factory' +module Arproxy class ProxyChain attr_reader :head, :tail @@ -11,6 +12,7 @@ def initialize(config) def setup @tail = ChainTail.new self + @patch = ConnectionAdapterPatches::PatchFactory.create(@config.adapter_class) @head = @config.proxies.reverse.inject(@tail) do |next_proxy, proxy_config| cls, options = proxy_config proxy = cls.new(*options) @@ -28,23 +30,11 @@ def reenable! end def enable! - @config.adapter_class.class_eval do - def raw_execute_with_arproxy(sql, name=nil, **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) - end - alias_method :raw_execute_without_arproxy, :raw_execute - alias_method :raw_execute, :raw_execute_with_arproxy - ::Arproxy.logger.debug('Arproxy: Enabled') - end + @patch.enable! end def disable! - @config.adapter_class.class_eval do - alias_method :raw_execute, :raw_execute_without_arproxy - ::Arproxy.logger.debug('Arproxy: Disabled') - end + @patch.disable! end def connection From e991c3224c8401acfd5d7727250723ece5931323 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 8 Sep 2024 21:14:49 +0900 Subject: [PATCH 11/68] Implement PostgresqlPatch --- integration_test/spec/postgresql_spec.rb | 4 +-- .../postgresql_patch.rb | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 lib/arproxy/connection_adapter_patches/postgresql_patch.rb diff --git a/integration_test/spec/postgresql_spec.rb b/integration_test/spec/postgresql_spec.rb index 737fe39..6139dc7 100644 --- a/integration_test/spec/postgresql_spec.rb +++ b/integration_test/spec/postgresql_spec.rb @@ -46,7 +46,7 @@ expect(Product.first.name).to eq('apple') expect(QueryLogger.log.size).to eq(2) - expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM `products` -- Hello Arproxy!') - expect(QueryLogger.log[1]).to eq('SELECT `products`.* FROM `products` ORDER BY `products`.`id` ASC LIMIT 1 -- Hello Arproxy!') + expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM "products" -- Hello Arproxy!') + expect(QueryLogger.log[1]).to eq('SELECT "products".* FROM "products" ORDER BY "products"."id" ASC LIMIT $1 -- Hello Arproxy!') end end diff --git a/lib/arproxy/connection_adapter_patches/postgresql_patch.rb b/lib/arproxy/connection_adapter_patches/postgresql_patch.rb new file mode 100644 index 0000000..2e4741e --- /dev/null +++ b/lib/arproxy/connection_adapter_patches/postgresql_patch.rb @@ -0,0 +1,36 @@ +require 'arproxy/connection_adapter_patches/base_patch' + +module Arproxy + module ConnectionAdapterPatches + class PostgresqlPatch < BasePatch + def enable! + adapter_class.class_eval do + def raw_execute_with_arproxy(sql, name=nil, **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) + end + alias_method :raw_execute_without_arproxy, :raw_execute + alias_method :raw_execute, :raw_execute_with_arproxy + + def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) + end + alias_method :internal_exec_query_without_arproxy, :internal_exec_query + alias_method :internal_exec_query, :internal_exec_query_with_arproxy + + ::Arproxy.logger.debug('Arproxy: Enabled') + end + end + + def disable! + adapter_class.class_eval do + alias_method :raw_execute, :raw_execute_without_arproxy + alias_method :internal_exec_query, :internal_exec_query_without_arproxy + end + end + end + end +end From fbbe06cd4f15333c258e301cd7f80c845a8bb52d Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 10:24:41 +0900 Subject: [PATCH 12/68] Add sqlserver implementation --- integration_test/.env | 3 ++ integration_test/Gemfile | 2 + integration_test/docker-compose.yml | 35 ++++++++++--- integration_test/gemfiles/ar_7.1.gemfile | 2 + integration_test/gemfiles/ar_7.2.gemfile | 2 + integration_test/spec/mysql2_spec.rb | 2 +- integration_test/spec/postgresql_spec.rb | 2 +- integration_test/spec/spec_helper.rb | 1 + integration_test/spec/sqlserver_spec.rb | 51 +++++++++++++++++++ integration_test/sql/sqlserver/init.sql | 10 ++++ .../patch_factory.rb | 2 +- .../sqlserver_patch.rb | 27 ++++++++++ 12 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 integration_test/.env create mode 100644 integration_test/spec/sqlserver_spec.rb create mode 100644 integration_test/sql/sqlserver/init.sql create mode 100644 lib/arproxy/connection_adapter_patches/sqlserver_patch.rb diff --git a/integration_test/.env b/integration_test/.env new file mode 100644 index 0000000..3c5debb --- /dev/null +++ b/integration_test/.env @@ -0,0 +1,3 @@ +ARPROXY_DB_USER="arproxy" +ARPROXY_DB_PASSWORD="4rpr0*y#2024" +ARPROXY_DB_DATABASE="arproxy_test" diff --git a/integration_test/Gemfile b/integration_test/Gemfile index 7577434..d655988 100644 --- a/integration_test/Gemfile +++ b/integration_test/Gemfile @@ -3,5 +3,7 @@ source 'https://rubygems.org' gem 'arproxy', path: '..' gem 'rspec' gem 'appraisal' +gem 'dotenv', require: 'dotenv/load' gem 'mysql2' gem 'pg' +gem 'activerecord-sqlserver-adapter' diff --git a/integration_test/docker-compose.yml b/integration_test/docker-compose.yml index 18ccaeb..0cd31f0 100644 --- a/integration_test/docker-compose.yml +++ b/integration_test/docker-compose.yml @@ -6,9 +6,9 @@ services: restart: always environment: MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_DATABASE: arproxy_test - MYSQL_USER: arproxy - MYSQL_PASSWORD: password + MYSQL_DATABASE: ${ARPROXY_DB_DATABASE} + MYSQL_USER: ${ARPROXY_DB_USER} + MYSQL_PASSWORD: ${ARPROXY_DB_PASSWORD} ports: - "23306:3306" @@ -16,8 +16,31 @@ services: image: postgres:16 restart: always environment: - POSTGRES_DB: arproxy_test - POSTGRES_USER: arproxy - POSTGRES_PASSWORD: password + POSTGRES_DB: ${ARPROXY_DB_DATABASE} + POSTGRES_USER: ${ARPROXY_DB_USER} + POSTGRES_PASSWORD: ${ARPROXY_DB_PASSWORD} ports: - "25432:5432" + + sqlserver: + image: mcr.microsoft.com/mssql/server:2022-latest + restart: always + environment: + ACCEPT_EULA: Y + MSSQL_SA_PASSWORD: R00tPassword12! + ports: + - "21433:1433" + healthcheck: + test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P R00tPassword12! -Q 'SELECT 1' || exit 1"] + interval: 5s + retries: 10 + start_period: 10s + + sqlserver-init: + image: mcr.microsoft.com/mssql/server:2022-latest + volumes: + - ./sql/sqlserver/init.sql:/init.sql + command: /opt/mssql-tools18/bin/sqlcmd -C -S sqlserver -U sa -P R00tPassword12! -d master -i /init.sql + depends_on: + sqlserver: + condition: service_healthy diff --git a/integration_test/gemfiles/ar_7.1.gemfile b/integration_test/gemfiles/ar_7.1.gemfile index 5273122..c2aefa1 100644 --- a/integration_test/gemfiles/ar_7.1.gemfile +++ b/integration_test/gemfiles/ar_7.1.gemfile @@ -5,6 +5,8 @@ source "https://rubygems.org" gem "arproxy", path: "../.." gem "rspec" gem "appraisal" +gem "dotenv", require: "dotenv/load" gem "mysql2" gem "pg" +gem "activerecord-sqlserver-adapter" gem "activerecord", "~> 7.1.0" diff --git a/integration_test/gemfiles/ar_7.2.gemfile b/integration_test/gemfiles/ar_7.2.gemfile index 39617ad..85a9c2f 100644 --- a/integration_test/gemfiles/ar_7.2.gemfile +++ b/integration_test/gemfiles/ar_7.2.gemfile @@ -5,6 +5,8 @@ source "https://rubygems.org" gem "arproxy", path: "../.." gem "rspec" gem "appraisal" +gem "dotenv", require: "dotenv/load" gem "mysql2" gem "pg" +gem "activerecord-sqlserver-adapter" gem "activerecord", "~> 7.2.0" diff --git a/integration_test/spec/mysql2_spec.rb b/integration_test/spec/mysql2_spec.rb index d5dca8a..6bf7e82 100644 --- a/integration_test/spec/mysql2_spec.rb +++ b/integration_test/spec/mysql2_spec.rb @@ -9,7 +9,7 @@ port: ENV.fetch('MYSQL_PORT', '23306').to_i, database: 'arproxy_test', username: 'arproxy', - password: 'password' + password: ENV.fetch('ARPROXY_DB_PASSWORD') ) Arproxy.configure do |config| diff --git a/integration_test/spec/postgresql_spec.rb b/integration_test/spec/postgresql_spec.rb index 6139dc7..e02e04d 100644 --- a/integration_test/spec/postgresql_spec.rb +++ b/integration_test/spec/postgresql_spec.rb @@ -9,7 +9,7 @@ port: ENV.fetch('POSTGRES_PORT', '25432').to_i, database: 'arproxy_test', username: 'arproxy', - password: 'password' + password: '4rpr0*y#2024' ) Arproxy.configure do |config| diff --git a/integration_test/spec/spec_helper.rb b/integration_test/spec/spec_helper.rb index 30ddfef..6902944 100644 --- a/integration_test/spec/spec_helper.rb +++ b/integration_test/spec/spec_helper.rb @@ -1,5 +1,6 @@ require 'arproxy' require 'active_record' +require 'dotenv/load' class Product < ActiveRecord::Base end diff --git a/integration_test/spec/sqlserver_spec.rb b/integration_test/spec/sqlserver_spec.rb new file mode 100644 index 0000000..cf68005 --- /dev/null +++ b/integration_test/spec/sqlserver_spec.rb @@ -0,0 +1,51 @@ +require_relative 'spec_helper' + +context 'SQLServer' do + before(:all) do + ActiveRecord::Base.establish_connection( + adapter: 'sqlserver', + host: ENV.fetch('MSSQL_HOST', '127.0.0.1'), + port: ENV.fetch('MSSQL_PORT', '21433').to_i, + database: 'arproxy_test', + username: 'arproxy', + password: '4rpr0*y#2024' + ) + + Arproxy.configure do |config| + config.adapter = 'sqlserver' + config.use HelloProxy + config.use QueryLogger + end + Arproxy.enable! + + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + + Product.create(name: 'apple', price: 100) + Product.create(name: 'banana', price: 200) + Product.create(name: 'orange', price: 300) + end + + after(:all) do + ActiveRecord::Base.connection.drop_table :products + ActiveRecord::Base.connection.close + Arproxy.disable! + end + + before(:each) do + QueryLogger.reset! + end + + it do + expect(QueryLogger.log.size).to eq(0) + + expect(Product.count).to eq(3) + expect(Product.first.name).to eq('apple') + + expect(QueryLogger.log.size).to eq(2) + expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM "products" -- Hello Arproxy!') + expect(QueryLogger.log[1]).to eq('SELECT "products".* FROM "products" ORDER BY "products"."id" ASC LIMIT $1 -- Hello Arproxy!') + end +end diff --git a/integration_test/sql/sqlserver/init.sql b/integration_test/sql/sqlserver/init.sql new file mode 100644 index 0000000..e6f0f76 --- /dev/null +++ b/integration_test/sql/sqlserver/init.sql @@ -0,0 +1,10 @@ +CREATE DATABASE arproxy_test; +GO +USE arproxy_test; +GO +CREATE LOGIN arproxy WITH PASSWORD = '4rpr0*y#2024'; +GO +CREATE USER arproxy FOR LOGIN arproxy; +GO +ALTER ROLE db_owner ADD MEMBER arproxy; +GO diff --git a/lib/arproxy/connection_adapter_patches/patch_factory.rb b/lib/arproxy/connection_adapter_patches/patch_factory.rb index 894cf8c..dbe74b4 100644 --- a/lib/arproxy/connection_adapter_patches/patch_factory.rb +++ b/lib/arproxy/connection_adapter_patches/patch_factory.rb @@ -1,6 +1,6 @@ module Arproxy module ConnectionAdapterPatches - SUPPORTED_ADAPTERS = %w[mysql2 postgresql] + SUPPORTED_ADAPTERS = %w[mysql2 postgresql sqlserver] class PatchFactory def self.create(adapter_class) patch_class(adapter_class).new(adapter_class) diff --git a/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb b/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb new file mode 100644 index 0000000..a7fc816 --- /dev/null +++ b/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb @@ -0,0 +1,27 @@ +require 'arproxy/connection_adapter_patches/base_patch' + +module Arproxy + module ConnectionAdapterPatches + class SqlserverPatch < BasePatch + def enable! + adapter_class.class_eval do + def raw_execute_with_arproxy(sql, name=nil, **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) + end + alias_method :raw_execute_without_arproxy, :raw_execute + alias_method :raw_execute, :raw_execute_with_arproxy + ::Arproxy.logger.debug('Arproxy: Enabled') + end + end + + def disable! + adapter_class.class_eval do + alias_method :raw_execute, :raw_execute_without_arproxy + ::Arproxy.logger.debug('Arproxy: Disabled') + end + end + end + end +end From 3a0d55c245cd202cc005e4f1bad8fbc08c8a165f Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 14:12:38 +0900 Subject: [PATCH 13/68] Add missing log --- lib/arproxy/connection_adapter_patches/postgresql_patch.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/arproxy/connection_adapter_patches/postgresql_patch.rb b/lib/arproxy/connection_adapter_patches/postgresql_patch.rb index 2e4741e..eb0e09a 100644 --- a/lib/arproxy/connection_adapter_patches/postgresql_patch.rb +++ b/lib/arproxy/connection_adapter_patches/postgresql_patch.rb @@ -29,6 +29,7 @@ def disable! adapter_class.class_eval do alias_method :raw_execute, :raw_execute_without_arproxy alias_method :internal_exec_query, :internal_exec_query_without_arproxy + ::Arproxy.logger.debug('Arproxy: Disabled') end end end From 3c22c34aca27c43103c9bb72be5bf53e06e82ded Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 14:13:02 +0900 Subject: [PATCH 14/68] ARPROXY_DB_PASSWORD --- integration_test/spec/postgresql_spec.rb | 2 +- integration_test/spec/sqlserver_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration_test/spec/postgresql_spec.rb b/integration_test/spec/postgresql_spec.rb index e02e04d..799dd8c 100644 --- a/integration_test/spec/postgresql_spec.rb +++ b/integration_test/spec/postgresql_spec.rb @@ -9,7 +9,7 @@ port: ENV.fetch('POSTGRES_PORT', '25432').to_i, database: 'arproxy_test', username: 'arproxy', - password: '4rpr0*y#2024' + password: ENV.fetch('ARPROXY_DB_PASSWORD') ) Arproxy.configure do |config| diff --git a/integration_test/spec/sqlserver_spec.rb b/integration_test/spec/sqlserver_spec.rb index cf68005..4d00748 100644 --- a/integration_test/spec/sqlserver_spec.rb +++ b/integration_test/spec/sqlserver_spec.rb @@ -8,7 +8,7 @@ port: ENV.fetch('MSSQL_PORT', '21433').to_i, database: 'arproxy_test', username: 'arproxy', - password: '4rpr0*y#2024' + password: ENV.fetch('ARPROXY_DB_PASSWORD') ) Arproxy.configure do |config| From 5b46049d02fdaff6009d8e62f5c3754a23b0ee5c Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 14:13:25 +0900 Subject: [PATCH 15/68] Implement sqlserver_patch --- integration_test/spec/sqlserver_spec.rb | 4 ++-- .../connection_adapter_patches/sqlserver_patch.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/integration_test/spec/sqlserver_spec.rb b/integration_test/spec/sqlserver_spec.rb index 4d00748..eba042f 100644 --- a/integration_test/spec/sqlserver_spec.rb +++ b/integration_test/spec/sqlserver_spec.rb @@ -45,7 +45,7 @@ expect(Product.first.name).to eq('apple') expect(QueryLogger.log.size).to eq(2) - expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM "products" -- Hello Arproxy!') - expect(QueryLogger.log[1]).to eq('SELECT "products".* FROM "products" ORDER BY "products"."id" ASC LIMIT $1 -- Hello Arproxy!') + expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM [products] -- Hello Arproxy!') + expect(QueryLogger.log[1]).to match(/\ASELECT \[products\].* FROM \[products\] ORDER BY \[products\]\.\[id\] ASC .* -- Hello Arproxy!\z/) end end diff --git a/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb b/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb index a7fc816..67ca3c0 100644 --- a/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb +++ b/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb @@ -12,6 +12,15 @@ def raw_execute_with_arproxy(sql, name=nil, **kwargs) end alias_method :raw_execute_without_arproxy, :raw_execute alias_method :raw_execute, :raw_execute_with_arproxy + + def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) + end + alias_method :internal_exec_query_without_arproxy, :internal_exec_query + alias_method :internal_exec_query, :internal_exec_query_with_arproxy + ::Arproxy.logger.debug('Arproxy: Enabled') end end @@ -19,6 +28,7 @@ def raw_execute_with_arproxy(sql, name=nil, **kwargs) def disable! adapter_class.class_eval do alias_method :raw_execute, :raw_execute_without_arproxy + alias_method :internal_exec_query, :internal_exec_query_without_arproxy ::Arproxy.logger.debug('Arproxy: Disabled') end end From 3497eda9620a4ab23897e9a578e685bda5b62050 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 15:37:22 +0900 Subject: [PATCH 16/68] AR7.2: register sqlserver --- integration_test/spec/sqlserver_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/integration_test/spec/sqlserver_spec.rb b/integration_test/spec/sqlserver_spec.rb index eba042f..e664e5a 100644 --- a/integration_test/spec/sqlserver_spec.rb +++ b/integration_test/spec/sqlserver_spec.rb @@ -2,6 +2,13 @@ context 'SQLServer' do before(:all) do + if ActiveRecord.version >= '7.2' + ActiveRecord::ConnectionAdapters.register( + 'sqlserver', + 'ActiveRecord::ConnectionAdapters::SQLServerAdapter', + 'active_record/connection_adapters/sqlserver_adapter' + ) + end ActiveRecord::Base.establish_connection( adapter: 'sqlserver', host: ENV.fetch('MSSQL_HOST', '127.0.0.1'), From 86362c441a65b4d44d1eb351227501cea5526bd9 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 15:58:49 +0900 Subject: [PATCH 17/68] clear config on disable! --- lib/arproxy.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/arproxy.rb b/lib/arproxy.rb index 27a1d06..694933b 100644 --- a/lib/arproxy.rb +++ b/lib/arproxy.rb @@ -44,6 +44,9 @@ def disable! @proxy_chain.disable! @proxy_chain = nil end + + clear_configuration + @enabled = false end From 971e9d7adeef68ab2dac60514bb51bccd8d0c3cf Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 16:02:28 +0900 Subject: [PATCH 18/68] Test MSSQL --- .github/workflows/integration_test.yml | 31 +++----------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index f1caa13..4a2ed24 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -23,33 +23,8 @@ jobs: env: BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile MYSQL_HOST: 127.0.0.1 - strategy: - matrix: - gemfile: - - ar_7.1 - - ar_7.2 - steps: - - uses: actions/checkout@v4 - - name: Start DB - run: docker compose up -d mysql - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ env.RUBY_VERSION }} - - name: Run bundle install - run: bundle install - - name: Run integration test - run: bundle exec rspec spec/mysql2_spec.rb - - postgresql: - continue-on-error: true - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./integration_test - env: - BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile POSTGRES_HOST: 127.0.0.1 + MSSQL_HOST: 127.0.0.1 strategy: matrix: gemfile: @@ -58,7 +33,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Start DB - run: docker compose up -d postgres + run: docker compose up -d - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -66,4 +41,4 @@ jobs: - name: Run bundle install run: bundle install - name: Run integration test - run: bundle exec rspec spec/postgresql_spec.rb + run: bundle exec rspec From 8c8de554b015de99a03260d07144e6c509286d68 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 16:07:05 +0900 Subject: [PATCH 19/68] install freetds-common --- .github/workflows/integration_test.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 4a2ed24..7cacc3e 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -14,7 +14,7 @@ env: RUBY_VERSION: 3.3 jobs: - mysql: + integration_test: continue-on-error: true runs-on: ubuntu-latest defaults: @@ -32,12 +32,16 @@ jobs: - ar_7.2 steps: - uses: actions/checkout@v4 - - name: Start DB - run: docker compose up -d + - name: Install freetds-common + run: | + sudo apt-get update + sudo apt-get install -y freetds-common - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ env.RUBY_VERSION }} + - name: Start DB + run: docker compose up -d - name: Run bundle install run: bundle install - name: Run integration test From f8fac78530efa50dba465a5b33f2e99f269f66c2 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 16:23:23 +0900 Subject: [PATCH 20/68] Add build deps for sqlserver --- .github/workflows/integration_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 7cacc3e..c568532 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -34,8 +34,8 @@ jobs: - uses: actions/checkout@v4 - name: Install freetds-common run: | - sudo apt-get update - sudo apt-get install -y freetds-common + sudo apt update + sudo apt install -y build-essential freetds-dev - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 71cb9b3109c482a7b234751cf9c18dc97ff6812a Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 16:30:32 +0900 Subject: [PATCH 21/68] fix comment --- .github/workflows/integration_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index c568532..c603896 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -32,7 +32,7 @@ jobs: - ar_7.2 steps: - uses: actions/checkout@v4 - - name: Install freetds-common + - name: Install build dependencies run: | sudo apt update sudo apt install -y build-essential freetds-dev From 01018f6d0b5e9afe7cfe081136716fefa72b7dfd Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 16:56:16 +0900 Subject: [PATCH 22/68] Add sqlite3 --- integration_test/Gemfile | 1 + integration_test/gemfiles/ar_7.1.gemfile | 1 + integration_test/gemfiles/ar_7.2.gemfile | 1 + integration_test/spec/sqlite3_spec.rb | 48 +++++++++++++++++++ .../patch_factory.rb | 9 +++- .../sqlite3_patch.rb | 37 ++++++++++++++ 6 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 integration_test/spec/sqlite3_spec.rb create mode 100644 lib/arproxy/connection_adapter_patches/sqlite3_patch.rb diff --git a/integration_test/Gemfile b/integration_test/Gemfile index d655988..17947dd 100644 --- a/integration_test/Gemfile +++ b/integration_test/Gemfile @@ -7,3 +7,4 @@ gem 'dotenv', require: 'dotenv/load' gem 'mysql2' gem 'pg' gem 'activerecord-sqlserver-adapter' +gem 'sqlite3' diff --git a/integration_test/gemfiles/ar_7.1.gemfile b/integration_test/gemfiles/ar_7.1.gemfile index c2aefa1..59a476c 100644 --- a/integration_test/gemfiles/ar_7.1.gemfile +++ b/integration_test/gemfiles/ar_7.1.gemfile @@ -9,4 +9,5 @@ gem "dotenv", require: "dotenv/load" gem "mysql2" gem "pg" gem "activerecord-sqlserver-adapter" +gem "sqlite3" gem "activerecord", "~> 7.1.0" diff --git a/integration_test/gemfiles/ar_7.2.gemfile b/integration_test/gemfiles/ar_7.2.gemfile index 85a9c2f..4e3de01 100644 --- a/integration_test/gemfiles/ar_7.2.gemfile +++ b/integration_test/gemfiles/ar_7.2.gemfile @@ -9,4 +9,5 @@ gem "dotenv", require: "dotenv/load" gem "mysql2" gem "pg" gem "activerecord-sqlserver-adapter" +gem "sqlite3" gem "activerecord", "~> 7.2.0" diff --git a/integration_test/spec/sqlite3_spec.rb b/integration_test/spec/sqlite3_spec.rb new file mode 100644 index 0000000..410e356 --- /dev/null +++ b/integration_test/spec/sqlite3_spec.rb @@ -0,0 +1,48 @@ +require_relative 'spec_helper' +require 'sqlite3' + +context 'SQLite3' do + before(:all) do + ActiveRecord::Base.establish_connection( + adapter: 'sqlite3', + database: ':memory:' + ) + + Arproxy.configure do |config| + config.adapter = 'sqlite3' + config.use HelloProxy + config.use QueryLogger + end + Arproxy.enable! + + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + + Product.create(name: 'apple', price: 100) + Product.create(name: 'banana', price: 200) + Product.create(name: 'orange', price: 300) + end + + after(:all) do + ActiveRecord::Base.connection.drop_table :products + ActiveRecord::Base.connection.close + Arproxy.disable! + end + + before(:each) do + QueryLogger.reset! + end + + it do + expect(QueryLogger.log.size).to eq(0) + + expect(Product.count).to eq(3) + expect(Product.first.name).to eq('apple') + + expect(QueryLogger.log.size).to eq(2) + expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM "products" -- Hello Arproxy!') + expect(QueryLogger.log[1]).to match(/\ASELECT "products".* FROM "products" ORDER BY "products"."id" ASC LIMIT .* -- Hello Arproxy!\z/) + end +end diff --git a/lib/arproxy/connection_adapter_patches/patch_factory.rb b/lib/arproxy/connection_adapter_patches/patch_factory.rb index dbe74b4..5eb8dc9 100644 --- a/lib/arproxy/connection_adapter_patches/patch_factory.rb +++ b/lib/arproxy/connection_adapter_patches/patch_factory.rb @@ -1,6 +1,6 @@ module Arproxy module ConnectionAdapterPatches - SUPPORTED_ADAPTERS = %w[mysql2 postgresql sqlserver] + SUPPORTED_ADAPTERS = %w[mysql2 postgresql sqlserver sqlite sqlite3] class PatchFactory def self.create(adapter_class) patch_class(adapter_class).new(adapter_class) @@ -8,7 +8,12 @@ def self.create(adapter_class) private def self.patch_class(adapter_class) - adapter_name = adapter_class::ADAPTER_NAME.downcase + if matched = adapter_class.name.match(/::(\w+)Adapter$/) + adapter_name = matched[1].downcase + else + raise ArgumentError, "Unsupported adapter: #{adapter_class.name.inspect}" + end + if SUPPORTED_ADAPTERS.include?(adapter_name.to_s) require "arproxy/connection_adapter_patches/#{adapter_name}_patch" "Arproxy::ConnectionAdapterPatches::#{adapter_name.to_s.camelize}Patch".constantize diff --git a/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb b/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb new file mode 100644 index 0000000..f16f4b9 --- /dev/null +++ b/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb @@ -0,0 +1,37 @@ +require 'arproxy/connection_adapter_patches/base_patch' + +module Arproxy + module ConnectionAdapterPatches + class Sqlite3Patch < BasePatch + def enable! + adapter_class.class_eval do + def raw_execute_with_arproxy(sql, name=nil, **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) + end + alias_method :raw_execute_without_arproxy, :raw_execute + alias_method :raw_execute, :raw_execute_with_arproxy + + def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) + end + alias_method :internal_exec_query_without_arproxy, :internal_exec_query + alias_method :internal_exec_query, :internal_exec_query_with_arproxy + + ::Arproxy.logger.debug('Arproxy: Enabled') + end + end + + def disable! + adapter_class.class_eval do + alias_method :raw_execute, :raw_execute_without_arproxy + alias_method :internal_exec_query, :internal_exec_query_without_arproxy + ::Arproxy.logger.debug('Arproxy: Disabled') + end + end + end + end +end From 0ac49c796e141aea85b7f0d4581b8fc0b1c9e19e Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 18:03:16 +0900 Subject: [PATCH 23/68] Implement trilogy patch and downgrade mysql to 8.0 --- integration_test/Gemfile | 1 + integration_test/db/mysql/my.cnf | 3 ++ .../{sql => db}/sqlserver/init.sql | 0 integration_test/docker-compose.yml | 6 ++- integration_test/gemfiles/ar_7.1.gemfile | 1 + integration_test/gemfiles/ar_7.2.gemfile | 1 + integration_test/spec/trilogy_spec.rb | 51 +++++++++++++++++++ .../patch_factory.rb | 2 +- .../trilogy_patch.rb | 27 ++++++++++ 9 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 integration_test/db/mysql/my.cnf rename integration_test/{sql => db}/sqlserver/init.sql (100%) create mode 100644 integration_test/spec/trilogy_spec.rb create mode 100644 lib/arproxy/connection_adapter_patches/trilogy_patch.rb diff --git a/integration_test/Gemfile b/integration_test/Gemfile index 17947dd..124d39c 100644 --- a/integration_test/Gemfile +++ b/integration_test/Gemfile @@ -8,3 +8,4 @@ gem 'mysql2' gem 'pg' gem 'activerecord-sqlserver-adapter' gem 'sqlite3' +gem 'trilogy' diff --git a/integration_test/db/mysql/my.cnf b/integration_test/db/mysql/my.cnf new file mode 100644 index 0000000..943a446 --- /dev/null +++ b/integration_test/db/mysql/my.cnf @@ -0,0 +1,3 @@ +[mysqld] +# Trilogy issue: https://github.com/trilogy-libraries/trilogy/issues/26 +default-authentication-plugin = mysql_native_password diff --git a/integration_test/sql/sqlserver/init.sql b/integration_test/db/sqlserver/init.sql similarity index 100% rename from integration_test/sql/sqlserver/init.sql rename to integration_test/db/sqlserver/init.sql diff --git a/integration_test/docker-compose.yml b/integration_test/docker-compose.yml index 0cd31f0..d1d9a99 100644 --- a/integration_test/docker-compose.yml +++ b/integration_test/docker-compose.yml @@ -2,13 +2,15 @@ version: '3' services: mysql: - image: mysql:9.0 + image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: rootpassword MYSQL_DATABASE: ${ARPROXY_DB_DATABASE} MYSQL_USER: ${ARPROXY_DB_USER} MYSQL_PASSWORD: ${ARPROXY_DB_PASSWORD} + volumes: + - ./db/mysql/my.cnf:/etc/mysql/conf.d/my.cnf ports: - "23306:3306" @@ -39,7 +41,7 @@ services: sqlserver-init: image: mcr.microsoft.com/mssql/server:2022-latest volumes: - - ./sql/sqlserver/init.sql:/init.sql + - ./db/sqlserver/init.sql:/init.sql command: /opt/mssql-tools18/bin/sqlcmd -C -S sqlserver -U sa -P R00tPassword12! -d master -i /init.sql depends_on: sqlserver: diff --git a/integration_test/gemfiles/ar_7.1.gemfile b/integration_test/gemfiles/ar_7.1.gemfile index 59a476c..daa221e 100644 --- a/integration_test/gemfiles/ar_7.1.gemfile +++ b/integration_test/gemfiles/ar_7.1.gemfile @@ -10,4 +10,5 @@ gem "mysql2" gem "pg" gem "activerecord-sqlserver-adapter" gem "sqlite3" +gem "trilogy" gem "activerecord", "~> 7.1.0" diff --git a/integration_test/gemfiles/ar_7.2.gemfile b/integration_test/gemfiles/ar_7.2.gemfile index 4e3de01..68f1c9b 100644 --- a/integration_test/gemfiles/ar_7.2.gemfile +++ b/integration_test/gemfiles/ar_7.2.gemfile @@ -10,4 +10,5 @@ gem "mysql2" gem "pg" gem "activerecord-sqlserver-adapter" gem "sqlite3" +gem "trilogy" gem "activerecord", "~> 7.2.0" diff --git a/integration_test/spec/trilogy_spec.rb b/integration_test/spec/trilogy_spec.rb new file mode 100644 index 0000000..c538e94 --- /dev/null +++ b/integration_test/spec/trilogy_spec.rb @@ -0,0 +1,51 @@ +require_relative 'spec_helper' + +context 'Trilogy' do + before(:all) do + ActiveRecord::Base.establish_connection( + adapter: 'trilogy', + host: ENV.fetch('MYSQL_HOST', '127.0.0.1'), + port: ENV.fetch('MYSQL_PORT', '23306').to_i, + database: 'arproxy_test', + username: 'arproxy', + password: ENV.fetch('ARPROXY_DB_PASSWORD') + ) + + Arproxy.configure do |config| + config.adapter = 'trilogy' + config.use HelloProxy + config.use QueryLogger + end + Arproxy.enable! + + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + + Product.create(name: 'apple', price: 100) + Product.create(name: 'banana', price: 200) + Product.create(name: 'orange', price: 300) + end + + after(:all) do + ActiveRecord::Base.connection.drop_table :products + ActiveRecord::Base.connection.close + Arproxy.disable! + end + + before(:each) do + QueryLogger.reset! + end + + it do + expect(QueryLogger.log.size).to eq(0) + + expect(Product.count).to eq(3) + expect(Product.first.name).to eq('apple') + + expect(QueryLogger.log.size).to eq(2) + expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM `products` -- Hello Arproxy!') + expect(QueryLogger.log[1]).to eq('SELECT `products`.* FROM `products` ORDER BY `products`.`id` ASC LIMIT 1 -- Hello Arproxy!') + end +end diff --git a/lib/arproxy/connection_adapter_patches/patch_factory.rb b/lib/arproxy/connection_adapter_patches/patch_factory.rb index 5eb8dc9..9dba4e6 100644 --- a/lib/arproxy/connection_adapter_patches/patch_factory.rb +++ b/lib/arproxy/connection_adapter_patches/patch_factory.rb @@ -1,6 +1,6 @@ module Arproxy module ConnectionAdapterPatches - SUPPORTED_ADAPTERS = %w[mysql2 postgresql sqlserver sqlite sqlite3] + SUPPORTED_ADAPTERS = %w[mysql2 postgresql sqlserver sqlite3 trilogy] class PatchFactory def self.create(adapter_class) patch_class(adapter_class).new(adapter_class) diff --git a/lib/arproxy/connection_adapter_patches/trilogy_patch.rb b/lib/arproxy/connection_adapter_patches/trilogy_patch.rb new file mode 100644 index 0000000..99d8bac --- /dev/null +++ b/lib/arproxy/connection_adapter_patches/trilogy_patch.rb @@ -0,0 +1,27 @@ +require 'arproxy/connection_adapter_patches/base_patch' + +module Arproxy + module ConnectionAdapterPatches + class TrilogyPatch < BasePatch + def enable! + adapter_class.class_eval do + def raw_execute_with_arproxy(sql, name=nil, **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) + end + alias_method :raw_execute_without_arproxy, :raw_execute + alias_method :raw_execute, :raw_execute_with_arproxy + ::Arproxy.logger.debug('Arproxy: Enabled') + end + end + + def disable! + adapter_class.class_eval do + alias_method :raw_execute, :raw_execute_without_arproxy + ::Arproxy.logger.debug('Arproxy: Disabled') + end + end + end + end +end From 5dc155ed84c1ecf81aad8a49eba97c92a9f21994 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 19:11:29 +0900 Subject: [PATCH 24/68] Refactor patches --- .../connection_adapter_patches/base_patch.rb | 50 ++++++++++++++++++- .../mysql2_patch.rb | 18 +------ .../postgresql_patch.rb | 28 +---------- .../sqlite3_patch.rb | 28 +---------- .../sqlserver_patch.rb | 28 +---------- .../trilogy_patch.rb | 18 +------ 6 files changed, 54 insertions(+), 116 deletions(-) diff --git a/lib/arproxy/connection_adapter_patches/base_patch.rb b/lib/arproxy/connection_adapter_patches/base_patch.rb index 8e5cd13..6de85e0 100644 --- a/lib/arproxy/connection_adapter_patches/base_patch.rb +++ b/lib/arproxy/connection_adapter_patches/base_patch.rb @@ -5,6 +5,7 @@ class BasePatch def initialize(adapter_class) @adapter_class = adapter_class + @enabled_patches = Set.new end def enable! @@ -12,8 +13,55 @@ def enable! end def disable! - raise NotImplementedError + @enabled_patches.each do |target_method| + adapter_class.class_eval do + if respond_to?(:"#{target_method}_with_arproxy") + alias_method target_method, :"#{target_method}_without_arproxy" + remove_method :"#{target_method}_with_arproxy" + end + end + ::Arproxy.logger.debug("Arproxy: Disabled (#{adapter_class::ADAPTER_NAME})") + end end + + protected + def enable_patches(*target_methods) + target_methods.each do |target_method| + enable_patch(target_method) + end + ::Arproxy.logger.debug("Arproxy: Enabled (#{adapter_class::ADAPTER_NAME})") + end + + def enable_patch(target_method) + return if @enabled_patches.include?(target_method) + + case target_method + when :raw_execute + adapter_class.class_eval do + def raw_execute_with_arproxy(sql, name=nil, **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) + end + alias_method :raw_execute_without_arproxy, :raw_execute + alias_method :raw_execute, :raw_execute_with_arproxy + end + when :internal_exec_query + adapter_class.class_eval do + def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) + end + alias_method :internal_exec_query_without_arproxy, :internal_exec_query + alias_method :internal_exec_query, :internal_exec_query_with_arproxy + end + else + raise ArgumentError, "Unsupported method to patch: #{target_method}" + end + + @enabled_patches << target_method + end end end end diff --git a/lib/arproxy/connection_adapter_patches/mysql2_patch.rb b/lib/arproxy/connection_adapter_patches/mysql2_patch.rb index 54423e8..05fa226 100644 --- a/lib/arproxy/connection_adapter_patches/mysql2_patch.rb +++ b/lib/arproxy/connection_adapter_patches/mysql2_patch.rb @@ -4,23 +4,7 @@ module Arproxy module ConnectionAdapterPatches class Mysql2Patch < BasePatch def enable! - adapter_class.class_eval do - def raw_execute_with_arproxy(sql, name=nil, **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) - end - alias_method :raw_execute_without_arproxy, :raw_execute - alias_method :raw_execute, :raw_execute_with_arproxy - ::Arproxy.logger.debug('Arproxy: Enabled') - end - end - - def disable! - adapter_class.class_eval do - alias_method :raw_execute, :raw_execute_without_arproxy - ::Arproxy.logger.debug('Arproxy: Disabled') - end + enable_patches :raw_execute end end end diff --git a/lib/arproxy/connection_adapter_patches/postgresql_patch.rb b/lib/arproxy/connection_adapter_patches/postgresql_patch.rb index eb0e09a..3759895 100644 --- a/lib/arproxy/connection_adapter_patches/postgresql_patch.rb +++ b/lib/arproxy/connection_adapter_patches/postgresql_patch.rb @@ -4,33 +4,7 @@ module Arproxy module ConnectionAdapterPatches class PostgresqlPatch < BasePatch def enable! - adapter_class.class_eval do - def raw_execute_with_arproxy(sql, name=nil, **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) - end - alias_method :raw_execute_without_arproxy, :raw_execute - alias_method :raw_execute, :raw_execute_with_arproxy - - def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) - end - alias_method :internal_exec_query_without_arproxy, :internal_exec_query - alias_method :internal_exec_query, :internal_exec_query_with_arproxy - - ::Arproxy.logger.debug('Arproxy: Enabled') - end - end - - def disable! - adapter_class.class_eval do - alias_method :raw_execute, :raw_execute_without_arproxy - alias_method :internal_exec_query, :internal_exec_query_without_arproxy - ::Arproxy.logger.debug('Arproxy: Disabled') - end + enable_patches :raw_execute, :internal_exec_query end end end diff --git a/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb b/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb index f16f4b9..8d65869 100644 --- a/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb +++ b/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb @@ -4,33 +4,7 @@ module Arproxy module ConnectionAdapterPatches class Sqlite3Patch < BasePatch def enable! - adapter_class.class_eval do - def raw_execute_with_arproxy(sql, name=nil, **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) - end - alias_method :raw_execute_without_arproxy, :raw_execute - alias_method :raw_execute, :raw_execute_with_arproxy - - def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) - end - alias_method :internal_exec_query_without_arproxy, :internal_exec_query - alias_method :internal_exec_query, :internal_exec_query_with_arproxy - - ::Arproxy.logger.debug('Arproxy: Enabled') - end - end - - def disable! - adapter_class.class_eval do - alias_method :raw_execute, :raw_execute_without_arproxy - alias_method :internal_exec_query, :internal_exec_query_without_arproxy - ::Arproxy.logger.debug('Arproxy: Disabled') - end + enable_patches :raw_execute, :internal_exec_query end end end diff --git a/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb b/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb index 67ca3c0..363c442 100644 --- a/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb +++ b/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb @@ -4,33 +4,7 @@ module Arproxy module ConnectionAdapterPatches class SqlserverPatch < BasePatch def enable! - adapter_class.class_eval do - def raw_execute_with_arproxy(sql, name=nil, **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) - end - alias_method :raw_execute_without_arproxy, :raw_execute - alias_method :raw_execute, :raw_execute_with_arproxy - - def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) - end - alias_method :internal_exec_query_without_arproxy, :internal_exec_query - alias_method :internal_exec_query, :internal_exec_query_with_arproxy - - ::Arproxy.logger.debug('Arproxy: Enabled') - end - end - - def disable! - adapter_class.class_eval do - alias_method :raw_execute, :raw_execute_without_arproxy - alias_method :internal_exec_query, :internal_exec_query_without_arproxy - ::Arproxy.logger.debug('Arproxy: Disabled') - end + enable_patches :raw_execute, :internal_exec_query end end end diff --git a/lib/arproxy/connection_adapter_patches/trilogy_patch.rb b/lib/arproxy/connection_adapter_patches/trilogy_patch.rb index 99d8bac..0243eb9 100644 --- a/lib/arproxy/connection_adapter_patches/trilogy_patch.rb +++ b/lib/arproxy/connection_adapter_patches/trilogy_patch.rb @@ -4,23 +4,7 @@ module Arproxy module ConnectionAdapterPatches class TrilogyPatch < BasePatch def enable! - adapter_class.class_eval do - def raw_execute_with_arproxy(sql, name=nil, **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) - end - alias_method :raw_execute_without_arproxy, :raw_execute - alias_method :raw_execute, :raw_execute_with_arproxy - ::Arproxy.logger.debug('Arproxy: Enabled') - end - end - - def disable! - adapter_class.class_eval do - alias_method :raw_execute, :raw_execute_without_arproxy - ::Arproxy.logger.debug('Arproxy: Disabled') - end + enable_patches :raw_execute end end end From b73597b614f0eddba5e0ef8472e1e0e684c88ee4 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 9 Sep 2024 19:16:16 +0900 Subject: [PATCH 25/68] Refactor #enable_patch --- .../connection_adapter_patches/base_patch.rb | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/arproxy/connection_adapter_patches/base_patch.rb b/lib/arproxy/connection_adapter_patches/base_patch.rb index 6de85e0..c546335 100644 --- a/lib/arproxy/connection_adapter_patches/base_patch.rb +++ b/lib/arproxy/connection_adapter_patches/base_patch.rb @@ -24,7 +24,7 @@ def disable! end end - protected + private def enable_patches(*target_methods) target_methods.each do |target_method| enable_patch(target_method) @@ -35,29 +35,26 @@ def enable_patches(*target_methods) def enable_patch(target_method) return if @enabled_patches.include?(target_method) - case target_method - when :raw_execute - adapter_class.class_eval do + adapter_class.class_eval do + case target_method + when :raw_execute def raw_execute_with_arproxy(sql, name=nil, **kwargs) ::Arproxy.proxy_chain.connection = self _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) end - alias_method :raw_execute_without_arproxy, :raw_execute - alias_method :raw_execute, :raw_execute_with_arproxy - end - when :internal_exec_query - adapter_class.class_eval do + when :internal_exec_query def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) ::Arproxy.proxy_chain.connection = self _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) end - alias_method :internal_exec_query_without_arproxy, :internal_exec_query - alias_method :internal_exec_query, :internal_exec_query_with_arproxy + else + raise ArgumentError, "Unsupported method to patch: #{target_method}" end - else - raise ArgumentError, "Unsupported method to patch: #{target_method}" + + alias_method :"#{target_method}_without_arproxy", target_method + alias_method target_method, :"#{target_method}_with_arproxy" end @enabled_patches << target_method From ab0dec7ffba42deacdf907b982eb619fc469d1f8 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 10:16:39 +0900 Subject: [PATCH 26/68] Support AR 6.1 and 7.0 --- .github/workflows/integration_test.yml | 2 ++ Appraisals | 8 ++++++++ arproxy.gemspec | 2 +- integration_test/gemfiles/ar_6.1.gemfile | 18 +++++++++++------- integration_test/gemfiles/ar_7.0.gemfile | 18 +++++++++++------- integration_test/spec/trilogy_spec.rb | 2 +- .../connection_adapter_patches/base_patch.rb | 12 ++++++++++++ .../postgresql_patch.rb | 6 +++++- .../sqlite3_patch.rb | 6 +++++- .../sqlserver_patch.rb | 6 +++++- 10 files changed, 61 insertions(+), 19 deletions(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index c603896..9977875 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -28,6 +28,8 @@ jobs: strategy: matrix: gemfile: + - ar_6.1 + - ar_7.0 - ar_7.1 - ar_7.2 steps: diff --git a/Appraisals b/Appraisals index 2f707bb..8149ed4 100644 --- a/Appraisals +++ b/Appraisals @@ -1,3 +1,11 @@ +appraise 'ar-6.1' do + gem 'activerecord', '~> 6.1.0' +end + +appraise 'ar-7.0' do + gem 'activerecord', '~> 7.0.0' +end + appraise 'ar-7.1' do gem 'activerecord', '~> 7.1.0' end diff --git a/arproxy.gemspec b/arproxy.gemspec index 69cd03e..eebf3b0 100644 --- a/arproxy.gemspec +++ b/arproxy.gemspec @@ -13,5 +13,5 @@ Gem::Specification.new do |spec| spec.license = 'MIT' spec.require_paths = ['lib'] - spec.add_dependency 'activerecord', '~> 7.1' + spec.add_dependency 'activerecord', '>= 6.1' end diff --git a/integration_test/gemfiles/ar_6.1.gemfile b/integration_test/gemfiles/ar_6.1.gemfile index 4f95002..f468ba6 100644 --- a/integration_test/gemfiles/ar_6.1.gemfile +++ b/integration_test/gemfiles/ar_6.1.gemfile @@ -1,10 +1,14 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'arproxy', path: '../..' -gem 'rspec' -gem 'appraisal' -gem 'mysql2' -gem 'pg' -gem 'activerecord', '~> 6.1.0' +gem "arproxy", path: "../.." +gem "rspec" +gem "appraisal" +gem "dotenv", require: "dotenv/load" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "sqlite3" +gem "trilogy" +gem "activerecord", "~> 6.1.0" diff --git a/integration_test/gemfiles/ar_7.0.gemfile b/integration_test/gemfiles/ar_7.0.gemfile index d78c2bb..5e29780 100644 --- a/integration_test/gemfiles/ar_7.0.gemfile +++ b/integration_test/gemfiles/ar_7.0.gemfile @@ -1,10 +1,14 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'arproxy', path: '../..' -gem 'rspec' -gem 'appraisal' -gem 'mysql2' -gem 'pg' -gem 'activerecord', '~> 7.0.0' +gem "arproxy", path: "../.." +gem "rspec" +gem "appraisal" +gem "dotenv", require: "dotenv/load" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "sqlite3" +gem "trilogy" +gem "activerecord", "~> 7.0.0" diff --git a/integration_test/spec/trilogy_spec.rb b/integration_test/spec/trilogy_spec.rb index c538e94..f23eabe 100644 --- a/integration_test/spec/trilogy_spec.rb +++ b/integration_test/spec/trilogy_spec.rb @@ -1,6 +1,6 @@ require_relative 'spec_helper' -context 'Trilogy' do +context 'Trilogy', if: ActiveRecord.version >= '7.1' do before(:all) do ActiveRecord::Base.establish_connection( adapter: 'trilogy', diff --git a/lib/arproxy/connection_adapter_patches/base_patch.rb b/lib/arproxy/connection_adapter_patches/base_patch.rb index c546335..b9be490 100644 --- a/lib/arproxy/connection_adapter_patches/base_patch.rb +++ b/lib/arproxy/connection_adapter_patches/base_patch.rb @@ -37,6 +37,18 @@ def enable_patch(target_method) adapter_class.class_eval do case target_method + when :execute # for AbstractMysqlAdapter, ActiveRecord 6.1 + def execute_with_arproxy(sql, name=nil, **kwargs) # + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:execute_without_arproxy, _sql, _name, **kwargs) + end + when :exec_query # for AbstractAdapter, ActiveRecord 6.1 + def exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) # + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:exec_query_without_arproxy, _sql, _name, binds, **kwargs) + end when :raw_execute def raw_execute_with_arproxy(sql, name=nil, **kwargs) ::Arproxy.proxy_chain.connection = self diff --git a/lib/arproxy/connection_adapter_patches/postgresql_patch.rb b/lib/arproxy/connection_adapter_patches/postgresql_patch.rb index 3759895..f42a001 100644 --- a/lib/arproxy/connection_adapter_patches/postgresql_patch.rb +++ b/lib/arproxy/connection_adapter_patches/postgresql_patch.rb @@ -4,7 +4,11 @@ module Arproxy module ConnectionAdapterPatches class PostgresqlPatch < BasePatch def enable! - enable_patches :raw_execute, :internal_exec_query + if ActiveRecord.version >= '7.1' + enable_patches :raw_execute, :internal_exec_query + else + enable_patches :execute, :exec_query + end end end end diff --git a/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb b/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb index 8d65869..50f75a4 100644 --- a/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb +++ b/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb @@ -4,7 +4,11 @@ module Arproxy module ConnectionAdapterPatches class Sqlite3Patch < BasePatch def enable! - enable_patches :raw_execute, :internal_exec_query + if ActiveRecord.version >= '7.1' + enable_patches :raw_execute, :internal_exec_query + else + enable_patches :execute, :exec_query + end end end end diff --git a/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb b/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb index 363c442..90a1f96 100644 --- a/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb +++ b/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb @@ -4,7 +4,11 @@ module Arproxy module ConnectionAdapterPatches class SqlserverPatch < BasePatch def enable! - enable_patches :raw_execute, :internal_exec_query + if ActiveRecord.version >= '7.1' + enable_patches :raw_execute, :internal_exec_query + else + enable_patches :execute, :exec_query + end end end end From 12f5b2a33d65ed7a5fb19afd750eb0792a6f22cc Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 10:23:37 +0900 Subject: [PATCH 27/68] Use #execute for AR6 --- lib/arproxy/connection_adapter_patches/mysql2_patch.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/arproxy/connection_adapter_patches/mysql2_patch.rb b/lib/arproxy/connection_adapter_patches/mysql2_patch.rb index 05fa226..c87ca4e 100644 --- a/lib/arproxy/connection_adapter_patches/mysql2_patch.rb +++ b/lib/arproxy/connection_adapter_patches/mysql2_patch.rb @@ -4,7 +4,11 @@ module Arproxy module ConnectionAdapterPatches class Mysql2Patch < BasePatch def enable! - enable_patches :raw_execute + if ActiveRecord.version >= '7.0' + enable_patches :raw_execute + else + enable_patches :execute + end end end end From 079c29869dacbe59c22beeb148768a915dca5451 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 11:28:35 +0900 Subject: [PATCH 28/68] Refactor ConnectionAdapterPatch --- lib/arproxy/connection_adapter_patch.rb | 86 +++++++++++++++++++ .../connection_adapter_patches/base_patch.rb | 76 ---------------- .../mysql2_patch.rb | 15 ---- .../patch_factory.rb | 26 ------ .../postgresql_patch.rb | 15 ---- .../sqlite3_patch.rb | 15 ---- .../sqlserver_patch.rb | 15 ---- .../trilogy_patch.rb | 11 --- lib/arproxy/proxy_chain.rb | 4 +- 9 files changed, 88 insertions(+), 175 deletions(-) create mode 100644 lib/arproxy/connection_adapter_patch.rb delete mode 100644 lib/arproxy/connection_adapter_patches/base_patch.rb delete mode 100644 lib/arproxy/connection_adapter_patches/mysql2_patch.rb delete mode 100644 lib/arproxy/connection_adapter_patches/patch_factory.rb delete mode 100644 lib/arproxy/connection_adapter_patches/postgresql_patch.rb delete mode 100644 lib/arproxy/connection_adapter_patches/sqlite3_patch.rb delete mode 100644 lib/arproxy/connection_adapter_patches/sqlserver_patch.rb delete mode 100644 lib/arproxy/connection_adapter_patches/trilogy_patch.rb diff --git a/lib/arproxy/connection_adapter_patch.rb b/lib/arproxy/connection_adapter_patch.rb new file mode 100644 index 0000000..7457501 --- /dev/null +++ b/lib/arproxy/connection_adapter_patch.rb @@ -0,0 +1,86 @@ +module Arproxy + class ConnectionAdapterPatch + attr_reader :adapter_class + + def initialize(adapter_class) + @adapter_class = adapter_class + @enabled_patches = Set.new + end + + def enable! + case adapter_class::ADAPTER_NAME + when 'Mysql2', 'Trilogy' # known children of AbstractMysqlAdapter + if ActiveRecord.version >= '7.0' + enable_patch :raw_execute + else + enable_patch :execute + end + when 'PostgreSQL', 'SQLServer', 'SQLite3' # known children of AbstractAdapter + if ActiveRecord.version >= '7.1' + enable_patch :raw_execute + enable_patch :internal_exec_query + else + enable_patch :execute + enable_patch :exec_query + end + else + raise Arproxy::Error, "Unexpected connection adapter: #{adapter_class&.name}" + end + ::Arproxy.logger.debug("Arproxy: Enabled (#{adapter_class::ADAPTER_NAME})") + end + + def disable! + @enabled_patches.dup.each do |target_method| + adapter_class.class_eval do + if respond_to?(:"#{target_method}_with_arproxy") + alias_method target_method, :"#{target_method}_without_arproxy" + remove_method :"#{target_method}_with_arproxy" + end + end + @enabled_patches.delete(target_method) + end + ::Arproxy.logger.debug("Arproxy: Disabled (#{adapter_class::ADAPTER_NAME})") + end + + private + def enable_patch(target_method) + return if @enabled_patches.include?(target_method) + + adapter_class.class_eval do + case target_method + when :execute # for AbstractMysqlAdapter, ActiveRecord 6.1 and earlier + def execute_with_arproxy(sql, name=nil, **kwargs) # + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:execute_without_arproxy, _sql, _name, **kwargs) + end + when :exec_query # for AbstractAdapter, ActiveRecord 7.0 and earlier + def exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) # + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:exec_query_without_arproxy, _sql, _name, binds, **kwargs) + end + when :raw_execute + def raw_execute_with_arproxy(sql, name=nil, **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) + end + when :internal_exec_query + def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) + end + else + raise ArgumentError, "Unsupported method to patch: #{target_method}" + end + + alias_method :"#{target_method}_without_arproxy", target_method + alias_method target_method, :"#{target_method}_with_arproxy" + end + + @enabled_patches << target_method + end + end +end diff --git a/lib/arproxy/connection_adapter_patches/base_patch.rb b/lib/arproxy/connection_adapter_patches/base_patch.rb deleted file mode 100644 index b9be490..0000000 --- a/lib/arproxy/connection_adapter_patches/base_patch.rb +++ /dev/null @@ -1,76 +0,0 @@ -module Arproxy - module ConnectionAdapterPatches - class BasePatch - attr_reader :adapter_class - - def initialize(adapter_class) - @adapter_class = adapter_class - @enabled_patches = Set.new - end - - def enable! - raise NotImplementedError - end - - def disable! - @enabled_patches.each do |target_method| - adapter_class.class_eval do - if respond_to?(:"#{target_method}_with_arproxy") - alias_method target_method, :"#{target_method}_without_arproxy" - remove_method :"#{target_method}_with_arproxy" - end - end - ::Arproxy.logger.debug("Arproxy: Disabled (#{adapter_class::ADAPTER_NAME})") - end - end - - private - def enable_patches(*target_methods) - target_methods.each do |target_method| - enable_patch(target_method) - end - ::Arproxy.logger.debug("Arproxy: Enabled (#{adapter_class::ADAPTER_NAME})") - end - - def enable_patch(target_method) - return if @enabled_patches.include?(target_method) - - adapter_class.class_eval do - case target_method - when :execute # for AbstractMysqlAdapter, ActiveRecord 6.1 - def execute_with_arproxy(sql, name=nil, **kwargs) # - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:execute_without_arproxy, _sql, _name, **kwargs) - end - when :exec_query # for AbstractAdapter, ActiveRecord 6.1 - def exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) # - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:exec_query_without_arproxy, _sql, _name, binds, **kwargs) - end - when :raw_execute - def raw_execute_with_arproxy(sql, name=nil, **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) - end - when :internal_exec_query - def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) - end - else - raise ArgumentError, "Unsupported method to patch: #{target_method}" - end - - alias_method :"#{target_method}_without_arproxy", target_method - alias_method target_method, :"#{target_method}_with_arproxy" - end - - @enabled_patches << target_method - end - end - end -end diff --git a/lib/arproxy/connection_adapter_patches/mysql2_patch.rb b/lib/arproxy/connection_adapter_patches/mysql2_patch.rb deleted file mode 100644 index c87ca4e..0000000 --- a/lib/arproxy/connection_adapter_patches/mysql2_patch.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'arproxy/connection_adapter_patches/base_patch' - -module Arproxy - module ConnectionAdapterPatches - class Mysql2Patch < BasePatch - def enable! - if ActiveRecord.version >= '7.0' - enable_patches :raw_execute - else - enable_patches :execute - end - end - end - end -end diff --git a/lib/arproxy/connection_adapter_patches/patch_factory.rb b/lib/arproxy/connection_adapter_patches/patch_factory.rb deleted file mode 100644 index 9dba4e6..0000000 --- a/lib/arproxy/connection_adapter_patches/patch_factory.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Arproxy - module ConnectionAdapterPatches - SUPPORTED_ADAPTERS = %w[mysql2 postgresql sqlserver sqlite3 trilogy] - class PatchFactory - def self.create(adapter_class) - patch_class(adapter_class).new(adapter_class) - end - - private - def self.patch_class(adapter_class) - if matched = adapter_class.name.match(/::(\w+)Adapter$/) - adapter_name = matched[1].downcase - else - raise ArgumentError, "Unsupported adapter: #{adapter_class.name.inspect}" - end - - if SUPPORTED_ADAPTERS.include?(adapter_name.to_s) - require "arproxy/connection_adapter_patches/#{adapter_name}_patch" - "Arproxy::ConnectionAdapterPatches::#{adapter_name.to_s.camelize}Patch".constantize - else - raise ArgumentError, "Unsupported adapter: #{adapter_name.inspect}" - end - end - end - end -end diff --git a/lib/arproxy/connection_adapter_patches/postgresql_patch.rb b/lib/arproxy/connection_adapter_patches/postgresql_patch.rb deleted file mode 100644 index f42a001..0000000 --- a/lib/arproxy/connection_adapter_patches/postgresql_patch.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'arproxy/connection_adapter_patches/base_patch' - -module Arproxy - module ConnectionAdapterPatches - class PostgresqlPatch < BasePatch - def enable! - if ActiveRecord.version >= '7.1' - enable_patches :raw_execute, :internal_exec_query - else - enable_patches :execute, :exec_query - end - end - end - end -end diff --git a/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb b/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb deleted file mode 100644 index 50f75a4..0000000 --- a/lib/arproxy/connection_adapter_patches/sqlite3_patch.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'arproxy/connection_adapter_patches/base_patch' - -module Arproxy - module ConnectionAdapterPatches - class Sqlite3Patch < BasePatch - def enable! - if ActiveRecord.version >= '7.1' - enable_patches :raw_execute, :internal_exec_query - else - enable_patches :execute, :exec_query - end - end - end - end -end diff --git a/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb b/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb deleted file mode 100644 index 90a1f96..0000000 --- a/lib/arproxy/connection_adapter_patches/sqlserver_patch.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'arproxy/connection_adapter_patches/base_patch' - -module Arproxy - module ConnectionAdapterPatches - class SqlserverPatch < BasePatch - def enable! - if ActiveRecord.version >= '7.1' - enable_patches :raw_execute, :internal_exec_query - else - enable_patches :execute, :exec_query - end - end - end - end -end diff --git a/lib/arproxy/connection_adapter_patches/trilogy_patch.rb b/lib/arproxy/connection_adapter_patches/trilogy_patch.rb deleted file mode 100644 index 0243eb9..0000000 --- a/lib/arproxy/connection_adapter_patches/trilogy_patch.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'arproxy/connection_adapter_patches/base_patch' - -module Arproxy - module ConnectionAdapterPatches - class TrilogyPatch < BasePatch - def enable! - enable_patches :raw_execute - end - end - end -end diff --git a/lib/arproxy/proxy_chain.rb b/lib/arproxy/proxy_chain.rb index 3297223..7fcd62a 100644 --- a/lib/arproxy/proxy_chain.rb +++ b/lib/arproxy/proxy_chain.rb @@ -1,5 +1,5 @@ require_relative './chain_tail' -require_relative './connection_adapter_patches/patch_factory' +require_relative './connection_adapter_patch' module Arproxy class ProxyChain @@ -12,7 +12,7 @@ def initialize(config) def setup @tail = ChainTail.new self - @patch = ConnectionAdapterPatches::PatchFactory.create(@config.adapter_class) + @patch = ConnectionAdapterPatch.new(@config.adapter_class) @head = @config.proxies.reverse.inject(@tail) do |next_proxy, proxy_config| cls, options = proxy_config proxy = cls.new(*options) From bf65480ec5572914f6bb4a95dca2eada8180304e Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 11:45:58 +0900 Subject: [PATCH 29/68] Fix broken sqlite3 --- Appraisals | 1 + integration_test/gemfiles/ar_6.1.gemfile | 2 +- lib/arproxy/connection_adapter_patch.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Appraisals b/Appraisals index 8149ed4..e523059 100644 --- a/Appraisals +++ b/Appraisals @@ -1,5 +1,6 @@ appraise 'ar-6.1' do gem 'activerecord', '~> 6.1.0' + gem 'sqlite3', '~> 1.4' end appraise 'ar-7.0' do diff --git a/integration_test/gemfiles/ar_6.1.gemfile b/integration_test/gemfiles/ar_6.1.gemfile index f468ba6..f5876ed 100644 --- a/integration_test/gemfiles/ar_6.1.gemfile +++ b/integration_test/gemfiles/ar_6.1.gemfile @@ -9,6 +9,6 @@ gem "dotenv", require: "dotenv/load" gem "mysql2" gem "pg" gem "activerecord-sqlserver-adapter" -gem "sqlite3" +gem "sqlite3", "~> 1.4" gem "trilogy" gem "activerecord", "~> 6.1.0" diff --git a/lib/arproxy/connection_adapter_patch.rb b/lib/arproxy/connection_adapter_patch.rb index 7457501..a27818b 100644 --- a/lib/arproxy/connection_adapter_patch.rb +++ b/lib/arproxy/connection_adapter_patch.rb @@ -15,7 +15,7 @@ def enable! else enable_patch :execute end - when 'PostgreSQL', 'SQLServer', 'SQLite3' # known children of AbstractAdapter + when 'PostgreSQL', 'SQLServer', 'SQLite' # known children of AbstractAdapter if ActiveRecord.version >= '7.1' enable_patch :raw_execute enable_patch :internal_exec_query From 8375c64c3cc7794c8b4181eec88d7e157a837d5f Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 11:48:08 +0900 Subject: [PATCH 30/68] Add AR version into spec contexts --- integration_test/spec/mysql2_spec.rb | 3 +-- integration_test/spec/postgresql_spec.rb | 3 +-- integration_test/spec/spec_helper.rb | 4 ++++ integration_test/spec/sqlite3_spec.rb | 3 +-- integration_test/spec/sqlserver_spec.rb | 2 +- integration_test/spec/trilogy_spec.rb | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/integration_test/spec/mysql2_spec.rb b/integration_test/spec/mysql2_spec.rb index 6bf7e82..06700c8 100644 --- a/integration_test/spec/mysql2_spec.rb +++ b/integration_test/spec/mysql2_spec.rb @@ -1,7 +1,6 @@ require_relative 'spec_helper' -require 'mysql2' -context 'MySQL' do +context "MySQL (AR #{ActiveRecord.version})" do before(:all) do ActiveRecord::Base.establish_connection( adapter: 'mysql2', diff --git a/integration_test/spec/postgresql_spec.rb b/integration_test/spec/postgresql_spec.rb index 799dd8c..005c0ad 100644 --- a/integration_test/spec/postgresql_spec.rb +++ b/integration_test/spec/postgresql_spec.rb @@ -1,7 +1,6 @@ require_relative 'spec_helper' -require 'pg' -context 'PostgreSQL' do +context "PostgreSQL (AR#{ar_version})" do before(:all) do ActiveRecord::Base.establish_connection( adapter: 'postgresql', diff --git a/integration_test/spec/spec_helper.rb b/integration_test/spec/spec_helper.rb index 6902944..6c25071 100644 --- a/integration_test/spec/spec_helper.rb +++ b/integration_test/spec/spec_helper.rb @@ -2,6 +2,10 @@ require 'active_record' require 'dotenv/load' +def ar_version + "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}" +end + class Product < ActiveRecord::Base end diff --git a/integration_test/spec/sqlite3_spec.rb b/integration_test/spec/sqlite3_spec.rb index 410e356..9b325bc 100644 --- a/integration_test/spec/sqlite3_spec.rb +++ b/integration_test/spec/sqlite3_spec.rb @@ -1,7 +1,6 @@ require_relative 'spec_helper' -require 'sqlite3' -context 'SQLite3' do +context "SQLite3 (AR#{ar_version})" do before(:all) do ActiveRecord::Base.establish_connection( adapter: 'sqlite3', diff --git a/integration_test/spec/sqlserver_spec.rb b/integration_test/spec/sqlserver_spec.rb index e664e5a..ad0384e 100644 --- a/integration_test/spec/sqlserver_spec.rb +++ b/integration_test/spec/sqlserver_spec.rb @@ -1,6 +1,6 @@ require_relative 'spec_helper' -context 'SQLServer' do +context "SQLServer (AR#{ar_version})" do before(:all) do if ActiveRecord.version >= '7.2' ActiveRecord::ConnectionAdapters.register( diff --git a/integration_test/spec/trilogy_spec.rb b/integration_test/spec/trilogy_spec.rb index f23eabe..089ef9e 100644 --- a/integration_test/spec/trilogy_spec.rb +++ b/integration_test/spec/trilogy_spec.rb @@ -1,6 +1,6 @@ require_relative 'spec_helper' -context 'Trilogy', if: ActiveRecord.version >= '7.1' do +context "Trilogy (AR#{ar_version})", if: ActiveRecord.version >= '7.1' do before(:all) do ActiveRecord::Base.establish_connection( adapter: 'trilogy', From 5c36ea4876165bfffed92912865220090d0215a9 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 14:45:59 +0900 Subject: [PATCH 31/68] shared_examples --- integration_test/spec/mysql2_spec.rb | 30 +---- integration_test/spec/postgresql_spec.rb | 28 +---- integration_test/spec/spec_helper.rb | 144 ++++++++++++++++++++++- integration_test/spec/sqlite3_spec.rb | 28 +---- integration_test/spec/sqlserver_spec.rb | 28 +---- integration_test/spec/trilogy_spec.rb | 28 +---- 6 files changed, 159 insertions(+), 127 deletions(-) diff --git a/integration_test/spec/mysql2_spec.rb b/integration_test/spec/mysql2_spec.rb index 06700c8..83effc6 100644 --- a/integration_test/spec/mysql2_spec.rb +++ b/integration_test/spec/mysql2_spec.rb @@ -1,6 +1,6 @@ require_relative 'spec_helper' -context "MySQL (AR #{ActiveRecord.version})" do +context "MySQL (AR#{ar_version})" do before(:all) do ActiveRecord::Base.establish_connection( adapter: 'mysql2', @@ -17,35 +17,13 @@ config.use QueryLogger end Arproxy.enable! - - ActiveRecord::Base.connection.create_table :products, force: true do |t| - t.string :name - t.integer :price - end - - Product.create(name: 'apple', price: 100) - Product.create(name: 'banana', price: 200) - Product.create(name: 'orange', price: 300) end after(:all) do - ActiveRecord::Base.connection.drop_table :products - ActiveRecord::Base.connection.close + cleanup_activerecord Arproxy.disable! end - before(:each) do - QueryLogger.reset! - end - - it do - expect(QueryLogger.log.size).to eq(0) - - expect(Product.count).to eq(3) - expect(Product.first.name).to eq('apple') - - expect(QueryLogger.log.size).to eq(2) - expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM `products` -- Hello Arproxy!') - expect(QueryLogger.log[1]).to eq('SELECT `products`.* FROM `products` ORDER BY `products`.`id` ASC LIMIT 1 -- Hello Arproxy!') - end + it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' + it_behaves_like 'Custom proxies work expectedly' end diff --git a/integration_test/spec/postgresql_spec.rb b/integration_test/spec/postgresql_spec.rb index 005c0ad..346dd4e 100644 --- a/integration_test/spec/postgresql_spec.rb +++ b/integration_test/spec/postgresql_spec.rb @@ -17,35 +17,13 @@ config.use QueryLogger end Arproxy.enable! - - ActiveRecord::Base.connection.create_table :products, force: true do |t| - t.string :name - t.integer :price - end - - Product.create(name: 'apple', price: 100) - Product.create(name: 'banana', price: 200) - Product.create(name: 'orange', price: 300) end after(:all) do - ActiveRecord::Base.connection.drop_table :products - ActiveRecord::Base.connection.close + cleanup_activerecord Arproxy.disable! end - before(:each) do - QueryLogger.reset! - end - - it do - expect(QueryLogger.log.size).to eq(0) - - expect(Product.count).to eq(3) - expect(Product.first.name).to eq('apple') - - expect(QueryLogger.log.size).to eq(2) - expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM "products" -- Hello Arproxy!') - expect(QueryLogger.log[1]).to eq('SELECT "products".* FROM "products" ORDER BY "products"."id" ASC LIMIT $1 -- Hello Arproxy!') - end + it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' + it_behaves_like 'Custom proxies work expectedly' end diff --git a/integration_test/spec/spec_helper.rb b/integration_test/spec/spec_helper.rb index 6c25071..3fb0c10 100644 --- a/integration_test/spec/spec_helper.rb +++ b/integration_test/spec/spec_helper.rb @@ -2,10 +2,150 @@ require 'active_record' require 'dotenv/load' +Arproxy.logger.level = Logger::WARN unless ENV['DEBUG'] + def ar_version "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}" end +def cleanup_activerecord + ActiveRecord::Base.connection.close + ActiveRecord::Base.connection.clear_cache! + ActiveRecord::Base.descendants.each(&:reset_column_information) + ActiveRecord::Base.connection.schema_cache.clear! +end + +RSpec::Matchers.define :add_query_log do |log_line_regex| + supports_block_expectations + + match do |block| + idx = QueryLogger.log.size + block.call + QueryLogger.log.size > idx && QueryLogger.log[idx..-1].any? { |log| log.match(log_line_regex) } + end + + failure_message do |block| + "expected to add query log matching #{log_line_regex.inspect}, but got #{QueryLogger.log.inspect}" + end + + failure_message_when_negated do |block| + "expected not to add query log matching #{log_line_regex.inspect}, but added" + end + + def supports_block_expectations? + true + end +end + +RSpec.shared_examples 'Arproxy does not break the original ActiveRecord functionality' do + before do + # CREATE + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + # INSERT + Product.create(name: 'apple', price: 100) + Product.create(name: 'banana', price: 200) + Product.create(name: 'orange', price: 300) + end + + after(:all) do + ActiveRecord::Base.connection.drop_table :products + end + + context 'SELECT' do + # it { expect(Product.where(name: ['apple', 'orange']).sum(:price)).to eq(400) } + it { expect(Product.count).to eq(3) } + end + + context 'UPDATE' do + it do + expect { + Product.where(name: 'banana').update_all(price: 1000) + }.to change { + Product.find_by(name: 'banana').price + }.from(200).to(1000) + end + end + + context 'DELETE' do + it do + expect { + Product.where(name: 'banana').delete_all + }.to change { + Product.where(name: 'banana').exists? + }.from(true).to(false) + end + end +end + +RSpec.shared_examples 'Custom proxies work expectedly' do + before do + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + Product.create(name: 'apple', price: 100) + Product.create(name: 'banana', price: 200) + Product.create(name: 'orange', price: 300) + QueryLogger.reset! + end + + after(:all) do + ActiveRecord::Base.connection.drop_table :products + end + + around do |example| + ActiveRecord::Base.uncached do + example.run + end + end + + context 'CREATE TABLE' do + it do + expect { + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + }.to add_query_log(/^CREATE TABLE.*products.*$/) + end + end + + context 'SELECT' do + it do + expect { + Product.where(name: ['apple', 'orange']).sum(:price) + }.to add_query_log(/^SELECT.*products.*$/) + end + end + + context 'INSERT' do + it do + expect { + Product.create(name: 'grape', price: 400) + }.to add_query_log(/^INSERT INTO.*products.*$/) + end + end + + context 'UPDATE' do + it do + expect { + Product.where(name: 'banana').update_all(price: 1000) + }.to add_query_log(/^UPDATE.*products.*$/) + end + end + + context 'DELETE' do + it do + expect { + Product.where(name: 'banana').delete_all + }.to add_query_log(/^DELETE.*products.*$/) + end + end +end + class Product < ActiveRecord::Base end @@ -13,7 +153,9 @@ class QueryLogger < Arproxy::Base def execute(sql, name = nil) @@log ||= [] @@log << sql - puts "QueryLogger: [#{name}] #{sql}" + if ENV['DEBUG'] + puts "QueryLogger: [#{name}] #{sql}" + end super end diff --git a/integration_test/spec/sqlite3_spec.rb b/integration_test/spec/sqlite3_spec.rb index 9b325bc..33ed754 100644 --- a/integration_test/spec/sqlite3_spec.rb +++ b/integration_test/spec/sqlite3_spec.rb @@ -13,35 +13,13 @@ config.use QueryLogger end Arproxy.enable! - - ActiveRecord::Base.connection.create_table :products, force: true do |t| - t.string :name - t.integer :price - end - - Product.create(name: 'apple', price: 100) - Product.create(name: 'banana', price: 200) - Product.create(name: 'orange', price: 300) end after(:all) do - ActiveRecord::Base.connection.drop_table :products - ActiveRecord::Base.connection.close + cleanup_activerecord Arproxy.disable! end - before(:each) do - QueryLogger.reset! - end - - it do - expect(QueryLogger.log.size).to eq(0) - - expect(Product.count).to eq(3) - expect(Product.first.name).to eq('apple') - - expect(QueryLogger.log.size).to eq(2) - expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM "products" -- Hello Arproxy!') - expect(QueryLogger.log[1]).to match(/\ASELECT "products".* FROM "products" ORDER BY "products"."id" ASC LIMIT .* -- Hello Arproxy!\z/) - end + it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' + it_behaves_like 'Custom proxies work expectedly' end diff --git a/integration_test/spec/sqlserver_spec.rb b/integration_test/spec/sqlserver_spec.rb index ad0384e..af3dee0 100644 --- a/integration_test/spec/sqlserver_spec.rb +++ b/integration_test/spec/sqlserver_spec.rb @@ -24,35 +24,13 @@ config.use QueryLogger end Arproxy.enable! - - ActiveRecord::Base.connection.create_table :products, force: true do |t| - t.string :name - t.integer :price - end - - Product.create(name: 'apple', price: 100) - Product.create(name: 'banana', price: 200) - Product.create(name: 'orange', price: 300) end after(:all) do - ActiveRecord::Base.connection.drop_table :products - ActiveRecord::Base.connection.close + cleanup_activerecord Arproxy.disable! end - before(:each) do - QueryLogger.reset! - end - - it do - expect(QueryLogger.log.size).to eq(0) - - expect(Product.count).to eq(3) - expect(Product.first.name).to eq('apple') - - expect(QueryLogger.log.size).to eq(2) - expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM [products] -- Hello Arproxy!') - expect(QueryLogger.log[1]).to match(/\ASELECT \[products\].* FROM \[products\] ORDER BY \[products\]\.\[id\] ASC .* -- Hello Arproxy!\z/) - end + it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' + it_behaves_like 'Custom proxies work expectedly' end diff --git a/integration_test/spec/trilogy_spec.rb b/integration_test/spec/trilogy_spec.rb index 089ef9e..c837714 100644 --- a/integration_test/spec/trilogy_spec.rb +++ b/integration_test/spec/trilogy_spec.rb @@ -17,35 +17,13 @@ config.use QueryLogger end Arproxy.enable! - - ActiveRecord::Base.connection.create_table :products, force: true do |t| - t.string :name - t.integer :price - end - - Product.create(name: 'apple', price: 100) - Product.create(name: 'banana', price: 200) - Product.create(name: 'orange', price: 300) end after(:all) do - ActiveRecord::Base.connection.drop_table :products - ActiveRecord::Base.connection.close + cleanup_activerecord Arproxy.disable! end - before(:each) do - QueryLogger.reset! - end - - it do - expect(QueryLogger.log.size).to eq(0) - - expect(Product.count).to eq(3) - expect(Product.first.name).to eq('apple') - - expect(QueryLogger.log.size).to eq(2) - expect(QueryLogger.log[0]).to eq('SELECT COUNT(*) FROM `products` -- Hello Arproxy!') - expect(QueryLogger.log[1]).to eq('SELECT `products`.* FROM `products` ORDER BY `products`.`id` ASC LIMIT 1 -- Hello Arproxy!') - end + it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' + it_behaves_like 'Custom proxies work expectedly' end From da0c1bb535c5394c1dd1e691f5530ade9599f2e0 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 15:43:49 +0900 Subject: [PATCH 32/68] Refactor patches and implement postgres specific patches --- lib/arproxy/connection_adapter_patch.rb | 82 ++++++++++++------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/lib/arproxy/connection_adapter_patch.rb b/lib/arproxy/connection_adapter_patch.rb index a27818b..736773e 100644 --- a/lib/arproxy/connection_adapter_patch.rb +++ b/lib/arproxy/connection_adapter_patch.rb @@ -4,24 +4,32 @@ class ConnectionAdapterPatch def initialize(adapter_class) @adapter_class = adapter_class - @enabled_patches = Set.new + @applied_patches = Set.new end def enable! case adapter_class::ADAPTER_NAME - when 'Mysql2', 'Trilogy' # known children of AbstractMysqlAdapter + when 'Mysql2', 'Trilogy' if ActiveRecord.version >= '7.0' - enable_patch :raw_execute + apply_patch :raw_execute else - enable_patch :execute + apply_patch :execute end - when 'PostgreSQL', 'SQLServer', 'SQLite' # known children of AbstractAdapter + when 'PostgreSQL' if ActiveRecord.version >= '7.1' - enable_patch :raw_execute - enable_patch :internal_exec_query + apply_patch :raw_execute else - enable_patch :execute - enable_patch :exec_query + apply_patch :execute + end + apply_patch_binds :exec_no_cache + apply_patch_binds :exec_cache + when 'SQLServer', 'SQLite' + if ActiveRecord.version >= '7.1' + apply_patch :raw_execute + apply_patch_binds :internal_exec_query + else + apply_patch :execute + apply_patch_binds :exec_query end else raise Arproxy::Error, "Unexpected connection adapter: #{adapter_class&.name}" @@ -30,57 +38,45 @@ def enable! end def disable! - @enabled_patches.dup.each do |target_method| + @applied_patches.dup.each do |target_method| adapter_class.class_eval do if respond_to?(:"#{target_method}_with_arproxy") alias_method target_method, :"#{target_method}_without_arproxy" remove_method :"#{target_method}_with_arproxy" end end - @enabled_patches.delete(target_method) + @applied_patches.delete(target_method) end ::Arproxy.logger.debug("Arproxy: Disabled (#{adapter_class::ADAPTER_NAME})") end private - def enable_patch(target_method) - return if @enabled_patches.include?(target_method) - + def apply_patch(target_method) + return if @applied_patches.include?(target_method) adapter_class.class_eval do - case target_method - when :execute # for AbstractMysqlAdapter, ActiveRecord 6.1 and earlier - def execute_with_arproxy(sql, name=nil, **kwargs) # - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:execute_without_arproxy, _sql, _name, **kwargs) - end - when :exec_query # for AbstractAdapter, ActiveRecord 7.0 and earlier - def exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) # - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:exec_query_without_arproxy, _sql, _name, binds, **kwargs) - end - when :raw_execute - def raw_execute_with_arproxy(sql, name=nil, **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:raw_execute_without_arproxy, _sql, _name, **kwargs) - end - when :internal_exec_query - def internal_exec_query_with_arproxy(sql, name=nil, binds=[], **kwargs) - ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send(:internal_exec_query_without_arproxy, _sql, _name, binds, **kwargs) - end - else - raise ArgumentError, "Unsupported method to patch: #{target_method}" + define_method("#{target_method}_with_arproxy") do |sql, name=nil, **kwargs| + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send("#{target_method}_without_arproxy", _sql, _name, **kwargs) end - alias_method :"#{target_method}_without_arproxy", target_method alias_method target_method, :"#{target_method}_with_arproxy" end + @applied_patches << target_method + end - @enabled_patches << target_method + def apply_patch_binds(target_method) + return if @applied_patches.include?(target_method) + adapter_class.class_eval do + define_method("#{target_method}_with_arproxy") do |sql, name=nil, binds=[], **kwargs| + ::Arproxy.proxy_chain.connection = self + _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) + self.send("#{target_method}_without_arproxy", _sql, _name, binds, **kwargs) + end + alias_method :"#{target_method}_without_arproxy", target_method + alias_method target_method, :"#{target_method}_with_arproxy" + end + @applied_patches << target_method end end end From 439404aaf78db89dba9491271dc45cb3c6858db2 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 15:44:22 +0900 Subject: [PATCH 33/68] Specify sqlite3 version for AR7.0 --- Appraisals | 1 + integration_test/gemfiles/ar_7.0.gemfile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Appraisals b/Appraisals index e523059..204b048 100644 --- a/Appraisals +++ b/Appraisals @@ -5,6 +5,7 @@ end appraise 'ar-7.0' do gem 'activerecord', '~> 7.0.0' + gem 'sqlite3', '~> 1.4' end appraise 'ar-7.1' do diff --git a/integration_test/gemfiles/ar_7.0.gemfile b/integration_test/gemfiles/ar_7.0.gemfile index 5e29780..21450e0 100644 --- a/integration_test/gemfiles/ar_7.0.gemfile +++ b/integration_test/gemfiles/ar_7.0.gemfile @@ -9,6 +9,6 @@ gem "dotenv", require: "dotenv/load" gem "mysql2" gem "pg" gem "activerecord-sqlserver-adapter" -gem "sqlite3" +gem "sqlite3", "~> 1.4" gem "trilogy" gem "activerecord", "~> 7.0.0" From 5cd183afaff529f884c1c3fe234ba8b772cfc580 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 15:49:51 +0900 Subject: [PATCH 34/68] Add some gems to suppress warnings --- Appraisals | 10 ++++++++++ integration_test/gemfiles/ar_6.1.gemfile | 3 +++ integration_test/gemfiles/ar_7.0.gemfile | 3 +++ 3 files changed, 16 insertions(+) diff --git a/Appraisals b/Appraisals index 204b048..fcb61c2 100644 --- a/Appraisals +++ b/Appraisals @@ -1,11 +1,21 @@ appraise 'ar-6.1' do gem 'activerecord', '~> 6.1.0' gem 'sqlite3', '~> 1.4' + + # required to suppress warnings + gem 'bigdecimal' + gem 'base64' + gem 'mutex_m' end appraise 'ar-7.0' do gem 'activerecord', '~> 7.0.0' gem 'sqlite3', '~> 1.4' + + # required to suppress warnings + gem 'bigdecimal' + gem 'base64' + gem 'mutex_m' end appraise 'ar-7.1' do diff --git a/integration_test/gemfiles/ar_6.1.gemfile b/integration_test/gemfiles/ar_6.1.gemfile index f5876ed..1082012 100644 --- a/integration_test/gemfiles/ar_6.1.gemfile +++ b/integration_test/gemfiles/ar_6.1.gemfile @@ -12,3 +12,6 @@ gem "activerecord-sqlserver-adapter" gem "sqlite3", "~> 1.4" gem "trilogy" gem "activerecord", "~> 6.1.0" +gem "bigdecimal" +gem "base64" +gem "mutex_m" diff --git a/integration_test/gemfiles/ar_7.0.gemfile b/integration_test/gemfiles/ar_7.0.gemfile index 21450e0..43a1952 100644 --- a/integration_test/gemfiles/ar_7.0.gemfile +++ b/integration_test/gemfiles/ar_7.0.gemfile @@ -12,3 +12,6 @@ gem "activerecord-sqlserver-adapter" gem "sqlite3", "~> 1.4" gem "trilogy" gem "activerecord", "~> 7.0.0" +gem "bigdecimal" +gem "base64" +gem "mutex_m" From 6635cea28d4c62ef86dba5addfe62646df366e38 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 15:51:23 +0900 Subject: [PATCH 35/68] Remove continu-on-error --- .github/workflows/integration_test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 9977875..db09cc2 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -15,7 +15,6 @@ env: jobs: integration_test: - continue-on-error: true runs-on: ubuntu-latest defaults: run: From 5b58f3c7fadd1ce12a86ec42c2f787c5226ec777 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 16:40:18 +0900 Subject: [PATCH 36/68] Split shared examples into files --- .../active_record_functions.rb | 42 +++++ .../spec/shared_examples/custom_proxies.rb | 115 +++++++++++++ integration_test/spec/spec_helper.rb | 161 +----------------- 3 files changed, 159 insertions(+), 159 deletions(-) create mode 100644 integration_test/spec/shared_examples/active_record_functions.rb create mode 100644 integration_test/spec/shared_examples/custom_proxies.rb diff --git a/integration_test/spec/shared_examples/active_record_functions.rb b/integration_test/spec/shared_examples/active_record_functions.rb new file mode 100644 index 0000000..9970d64 --- /dev/null +++ b/integration_test/spec/shared_examples/active_record_functions.rb @@ -0,0 +1,42 @@ +RSpec.shared_examples 'Arproxy does not break the original ActiveRecord functionality' do + before do + # CREATE + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + # INSERT + Product.create(name: 'apple', price: 100) + Product.create(name: 'banana', price: 200) + Product.create(name: 'orange', price: 300) + end + + after(:all) do + ActiveRecord::Base.connection.drop_table :products + end + + context 'SELECT' do + # it { expect(Product.where(name: ['apple', 'orange']).sum(:price)).to eq(400) } + it { expect(Product.count).to eq(3) } + end + + context 'UPDATE' do + it do + expect { + Product.where(name: 'banana').update_all(price: 1000) + }.to change { + Product.find_by(name: 'banana').price + }.from(200).to(1000) + end + end + + context 'DELETE' do + it do + expect { + Product.where(name: 'banana').delete_all + }.to change { + Product.where(name: 'banana').exists? + }.from(true).to(false) + end + end +end diff --git a/integration_test/spec/shared_examples/custom_proxies.rb b/integration_test/spec/shared_examples/custom_proxies.rb new file mode 100644 index 0000000..e6f3622 --- /dev/null +++ b/integration_test/spec/shared_examples/custom_proxies.rb @@ -0,0 +1,115 @@ +class Product < ActiveRecord::Base +end + +class QueryLogger < Arproxy::Base + def execute(sql, name = nil) + @@log ||= [] + @@log << sql + if ENV['DEBUG'] + puts "QueryLogger: [#{name}] #{sql}" + end + super + end + + def self.log + @@log + end + + def self.reset! + @@log = [] + end +end + +class HelloProxy < Arproxy::Base + def execute(sql, name = nil) + super("#{sql} -- Hello Arproxy!", name) + end +end + +RSpec::Matchers.define :add_query_log do |log_line_regex| + supports_block_expectations + + match do |block| + idx = QueryLogger.log.size + block.call + QueryLogger.log.size > idx && QueryLogger.log[idx..-1].any? { |log| log.match(log_line_regex) } + end + + failure_message do |block| + "expected to add query log matching #{log_line_regex.inspect}, but got #{QueryLogger.log.inspect}" + end + + failure_message_when_negated do |block| + "expected not to add query log matching #{log_line_regex.inspect}, but added" + end + + def supports_block_expectations? + true + end +end + +RSpec.shared_examples 'Custom proxies work expectedly' do + before do + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + Product.create(name: 'apple', price: 100) + Product.create(name: 'banana', price: 200) + Product.create(name: 'orange', price: 300) + QueryLogger.reset! + end + + after(:all) do + ActiveRecord::Base.connection.drop_table :products + end + + around do |example| + ActiveRecord::Base.uncached do + example.run + end + end + + context 'CREATE TABLE' do + it do + expect { + ActiveRecord::Base.connection.create_table :products, force: true do |t| + t.string :name + t.integer :price + end + }.to add_query_log(/^CREATE TABLE.*products.* -- Hello Arproxy!$/) + end + end + + context 'SELECT' do + it do + expect { + Product.where(name: ['apple', 'orange']).sum(:price) + }.to add_query_log(/^SELECT.*products.* -- Hello Arproxy!$/) + end + end + + context 'INSERT' do + it do + expect { + Product.create(name: 'grape', price: 400) + }.to add_query_log(/^INSERT INTO.*products.* -- Hello Arproxy!$/) + end + end + + context 'UPDATE' do + it do + expect { + Product.where(name: 'banana').update_all(price: 1000) + }.to add_query_log(/^UPDATE.*products.* -- Hello Arproxy!$/) + end + end + + context 'DELETE' do + it do + expect { + Product.where(name: 'banana').delete_all + }.to add_query_log(/^DELETE.*products.* -- Hello Arproxy!$/) + end + end +end diff --git a/integration_test/spec/spec_helper.rb b/integration_test/spec/spec_helper.rb index 3fb0c10..c9870e1 100644 --- a/integration_test/spec/spec_helper.rb +++ b/integration_test/spec/spec_helper.rb @@ -1,6 +1,8 @@ require 'arproxy' require 'active_record' require 'dotenv/load' +require_relative './shared_examples/custom_proxies' +require_relative './shared_examples/active_record_functions' Arproxy.logger.level = Logger::WARN unless ENV['DEBUG'] @@ -14,162 +16,3 @@ def cleanup_activerecord ActiveRecord::Base.descendants.each(&:reset_column_information) ActiveRecord::Base.connection.schema_cache.clear! end - -RSpec::Matchers.define :add_query_log do |log_line_regex| - supports_block_expectations - - match do |block| - idx = QueryLogger.log.size - block.call - QueryLogger.log.size > idx && QueryLogger.log[idx..-1].any? { |log| log.match(log_line_regex) } - end - - failure_message do |block| - "expected to add query log matching #{log_line_regex.inspect}, but got #{QueryLogger.log.inspect}" - end - - failure_message_when_negated do |block| - "expected not to add query log matching #{log_line_regex.inspect}, but added" - end - - def supports_block_expectations? - true - end -end - -RSpec.shared_examples 'Arproxy does not break the original ActiveRecord functionality' do - before do - # CREATE - ActiveRecord::Base.connection.create_table :products, force: true do |t| - t.string :name - t.integer :price - end - # INSERT - Product.create(name: 'apple', price: 100) - Product.create(name: 'banana', price: 200) - Product.create(name: 'orange', price: 300) - end - - after(:all) do - ActiveRecord::Base.connection.drop_table :products - end - - context 'SELECT' do - # it { expect(Product.where(name: ['apple', 'orange']).sum(:price)).to eq(400) } - it { expect(Product.count).to eq(3) } - end - - context 'UPDATE' do - it do - expect { - Product.where(name: 'banana').update_all(price: 1000) - }.to change { - Product.find_by(name: 'banana').price - }.from(200).to(1000) - end - end - - context 'DELETE' do - it do - expect { - Product.where(name: 'banana').delete_all - }.to change { - Product.where(name: 'banana').exists? - }.from(true).to(false) - end - end -end - -RSpec.shared_examples 'Custom proxies work expectedly' do - before do - ActiveRecord::Base.connection.create_table :products, force: true do |t| - t.string :name - t.integer :price - end - Product.create(name: 'apple', price: 100) - Product.create(name: 'banana', price: 200) - Product.create(name: 'orange', price: 300) - QueryLogger.reset! - end - - after(:all) do - ActiveRecord::Base.connection.drop_table :products - end - - around do |example| - ActiveRecord::Base.uncached do - example.run - end - end - - context 'CREATE TABLE' do - it do - expect { - ActiveRecord::Base.connection.create_table :products, force: true do |t| - t.string :name - t.integer :price - end - }.to add_query_log(/^CREATE TABLE.*products.*$/) - end - end - - context 'SELECT' do - it do - expect { - Product.where(name: ['apple', 'orange']).sum(:price) - }.to add_query_log(/^SELECT.*products.*$/) - end - end - - context 'INSERT' do - it do - expect { - Product.create(name: 'grape', price: 400) - }.to add_query_log(/^INSERT INTO.*products.*$/) - end - end - - context 'UPDATE' do - it do - expect { - Product.where(name: 'banana').update_all(price: 1000) - }.to add_query_log(/^UPDATE.*products.*$/) - end - end - - context 'DELETE' do - it do - expect { - Product.where(name: 'banana').delete_all - }.to add_query_log(/^DELETE.*products.*$/) - end - end -end - -class Product < ActiveRecord::Base -end - -class QueryLogger < Arproxy::Base - def execute(sql, name = nil) - @@log ||= [] - @@log << sql - if ENV['DEBUG'] - puts "QueryLogger: [#{name}] #{sql}" - end - super - end - - def self.log - @@log - end - - def self.reset! - @@log = [] - end -end - -class HelloProxy < Arproxy::Base - def execute(sql, name = nil) - super("#{sql} -- Hello Arproxy!", name) - end -end From 183329ec54c95dd59616f1401d040d1dc500594a Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 16:45:28 +0900 Subject: [PATCH 37/68] remove version (obsolete) --- integration_test/docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/integration_test/docker-compose.yml b/integration_test/docker-compose.yml index d1d9a99..267e2f3 100644 --- a/integration_test/docker-compose.yml +++ b/integration_test/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: mysql: image: mysql:8.0 From 56af7307d65f834b0e2b77ef2e598e11f6aab469 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Tue, 10 Sep 2024 17:27:30 +0900 Subject: [PATCH 38/68] Add integration test for plugin --- .../spec/arproxy/plugin/query_logger.rb | 22 +++++++++++++++++++ integration_test/spec/mysql2_spec.rb | 2 +- integration_test/spec/postgresql_spec.rb | 2 +- .../spec/shared_examples/custom_proxies.rb | 19 ---------------- integration_test/spec/sqlite3_spec.rb | 2 +- integration_test/spec/sqlserver_spec.rb | 2 +- integration_test/spec/trilogy_spec.rb | 2 +- 7 files changed, 27 insertions(+), 24 deletions(-) create mode 100644 integration_test/spec/arproxy/plugin/query_logger.rb diff --git a/integration_test/spec/arproxy/plugin/query_logger.rb b/integration_test/spec/arproxy/plugin/query_logger.rb new file mode 100644 index 0000000..11d4a07 --- /dev/null +++ b/integration_test/spec/arproxy/plugin/query_logger.rb @@ -0,0 +1,22 @@ +require 'arproxy/plugin' + +class QueryLogger < Arproxy::Base + Arproxy::Plugin.register(:query_logger, self) + + def execute(sql, name = nil) + @@log ||= [] + @@log << sql + if ENV['DEBUG'] + puts "QueryLogger: [#{name}] #{sql}" + end + super + end + + def self.log + @@log + end + + def self.reset! + @@log = [] + end +end diff --git a/integration_test/spec/mysql2_spec.rb b/integration_test/spec/mysql2_spec.rb index 83effc6..39afc84 100644 --- a/integration_test/spec/mysql2_spec.rb +++ b/integration_test/spec/mysql2_spec.rb @@ -14,7 +14,7 @@ Arproxy.configure do |config| config.adapter = 'mysql2' config.use HelloProxy - config.use QueryLogger + config.plugin :query_logger end Arproxy.enable! end diff --git a/integration_test/spec/postgresql_spec.rb b/integration_test/spec/postgresql_spec.rb index 346dd4e..0afc747 100644 --- a/integration_test/spec/postgresql_spec.rb +++ b/integration_test/spec/postgresql_spec.rb @@ -14,7 +14,7 @@ Arproxy.configure do |config| config.adapter = 'postgresql' config.use HelloProxy - config.use QueryLogger + config.plugin :query_logger end Arproxy.enable! end diff --git a/integration_test/spec/shared_examples/custom_proxies.rb b/integration_test/spec/shared_examples/custom_proxies.rb index e6f3622..91e33ae 100644 --- a/integration_test/spec/shared_examples/custom_proxies.rb +++ b/integration_test/spec/shared_examples/custom_proxies.rb @@ -1,25 +1,6 @@ class Product < ActiveRecord::Base end -class QueryLogger < Arproxy::Base - def execute(sql, name = nil) - @@log ||= [] - @@log << sql - if ENV['DEBUG'] - puts "QueryLogger: [#{name}] #{sql}" - end - super - end - - def self.log - @@log - end - - def self.reset! - @@log = [] - end -end - class HelloProxy < Arproxy::Base def execute(sql, name = nil) super("#{sql} -- Hello Arproxy!", name) diff --git a/integration_test/spec/sqlite3_spec.rb b/integration_test/spec/sqlite3_spec.rb index 33ed754..813927e 100644 --- a/integration_test/spec/sqlite3_spec.rb +++ b/integration_test/spec/sqlite3_spec.rb @@ -10,7 +10,7 @@ Arproxy.configure do |config| config.adapter = 'sqlite3' config.use HelloProxy - config.use QueryLogger + config.plugin :query_logger end Arproxy.enable! end diff --git a/integration_test/spec/sqlserver_spec.rb b/integration_test/spec/sqlserver_spec.rb index af3dee0..2d14a14 100644 --- a/integration_test/spec/sqlserver_spec.rb +++ b/integration_test/spec/sqlserver_spec.rb @@ -21,7 +21,7 @@ Arproxy.configure do |config| config.adapter = 'sqlserver' config.use HelloProxy - config.use QueryLogger + config.plugin :query_logger end Arproxy.enable! end diff --git a/integration_test/spec/trilogy_spec.rb b/integration_test/spec/trilogy_spec.rb index c837714..3255a41 100644 --- a/integration_test/spec/trilogy_spec.rb +++ b/integration_test/spec/trilogy_spec.rb @@ -14,7 +14,7 @@ Arproxy.configure do |config| config.adapter = 'trilogy' config.use HelloProxy - config.use QueryLogger + config.plugin :query_logger end Arproxy.enable! end From 5bdd7843ec6d1ca950a1c59718db8a22d24776b9 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 10:47:10 +0900 Subject: [PATCH 39/68] Refactor spec directories --- integration_test/.env => .env | 0 Gemfile | 6 ++++++ {integration_test/db => db}/mysql/my.cnf | 0 .../db => db}/sqlserver/init.sql | 0 .../docker-compose.yml => docker-compose.yml | 0 gemfiles/ar_6.1.gemfile | 19 ++++++++++++++++--- gemfiles/ar_7.0.gemfile | 19 ++++++++++++++++--- gemfiles/ar_7.1.gemfile | 6 ++++++ gemfiles/ar_7.2.gemfile | 6 ++++++ integration_test/spec/spec_helper.rb | 18 ------------------ .../arproxy/plugin/query_logger.rb | 0 spec/arproxy_spec.rb | 4 +++- spec/{arproxy => }/config_spec.rb | 0 .../spec => spec/integration}/mysql2_spec.rb | 2 +- .../integration}/postgresql_spec.rb | 2 +- .../active_record_functions.rb | 0 .../shared_examples/custom_proxies.rb | 0 .../spec => spec/integration}/sqlite3_spec.rb | 2 +- .../integration}/sqlserver_spec.rb | 2 +- .../spec => spec/integration}/trilogy_spec.rb | 2 +- spec/spec_helper.rb | 19 ++++++++++++++++++- 21 files changed, 76 insertions(+), 31 deletions(-) rename integration_test/.env => .env (100%) rename {integration_test/db => db}/mysql/my.cnf (100%) rename {integration_test/db => db}/sqlserver/init.sql (100%) rename integration_test/docker-compose.yml => docker-compose.yml (100%) delete mode 100644 integration_test/spec/spec_helper.rb rename {integration_test/spec => spec}/arproxy/plugin/query_logger.rb (100%) rename spec/{arproxy => }/config_spec.rb (100%) rename {integration_test/spec => spec/integration}/mysql2_spec.rb (95%) rename {integration_test/spec => spec/integration}/postgresql_spec.rb (95%) rename {integration_test/spec => spec/integration}/shared_examples/active_record_functions.rb (100%) rename {integration_test/spec => spec/integration}/shared_examples/custom_proxies.rb (100%) rename {integration_test/spec => spec/integration}/sqlite3_spec.rb (94%) rename {integration_test/spec => spec/integration}/sqlserver_spec.rb (96%) rename {integration_test/spec => spec/integration}/trilogy_spec.rb (95%) diff --git a/integration_test/.env b/.env similarity index 100% rename from integration_test/.env rename to .env diff --git a/Gemfile b/Gemfile index 0031a7f..3e06a76 100644 --- a/Gemfile +++ b/Gemfile @@ -6,3 +6,9 @@ gem 'rspec' gem 'appraisal' gem 'rubocop' gem 'rubocop-md' +gem 'dotenv', require: 'dotenv/load' +gem 'mysql2' +gem 'pg' +gem 'activerecord-sqlserver-adapter' +gem 'sqlite3' +gem 'trilogy' diff --git a/integration_test/db/mysql/my.cnf b/db/mysql/my.cnf similarity index 100% rename from integration_test/db/mysql/my.cnf rename to db/mysql/my.cnf diff --git a/integration_test/db/sqlserver/init.sql b/db/sqlserver/init.sql similarity index 100% rename from integration_test/db/sqlserver/init.sql rename to db/sqlserver/init.sql diff --git a/integration_test/docker-compose.yml b/docker-compose.yml similarity index 100% rename from integration_test/docker-compose.yml rename to docker-compose.yml diff --git a/gemfiles/ar_6.1.gemfile b/gemfiles/ar_6.1.gemfile index 242d68a..3a66ce6 100644 --- a/gemfiles/ar_6.1.gemfile +++ b/gemfiles/ar_6.1.gemfile @@ -1,7 +1,20 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'activerecord', '~> 6.1.0' +gem "rspec" +gem "appraisal" +gem "rubocop" +gem "rubocop-md" +gem "dotenv", require: "dotenv/load" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "sqlite3", "~> 1.4" +gem "trilogy" +gem "activerecord", "~> 6.1.0" +gem "bigdecimal" +gem "base64" +gem "mutex_m" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/ar_7.0.gemfile b/gemfiles/ar_7.0.gemfile index 73168da..0820eb6 100644 --- a/gemfiles/ar_7.0.gemfile +++ b/gemfiles/ar_7.0.gemfile @@ -1,7 +1,20 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'activerecord', '~> 7.0.0' +gem "rspec" +gem "appraisal" +gem "rubocop" +gem "rubocop-md" +gem "dotenv", require: "dotenv/load" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "sqlite3", "~> 1.4" +gem "trilogy" +gem "activerecord", "~> 7.0.0" +gem "bigdecimal" +gem "base64" +gem "mutex_m" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/ar_7.1.gemfile b/gemfiles/ar_7.1.gemfile index c325fd4..c11fb53 100644 --- a/gemfiles/ar_7.1.gemfile +++ b/gemfiles/ar_7.1.gemfile @@ -6,6 +6,12 @@ gem "rspec" gem "appraisal" gem "rubocop" gem "rubocop-md" +gem "dotenv", require: "dotenv/load" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "sqlite3" +gem "trilogy" gem "activerecord", "~> 7.1.0" gemspec path: "../" diff --git a/gemfiles/ar_7.2.gemfile b/gemfiles/ar_7.2.gemfile index 4020211..10babe6 100644 --- a/gemfiles/ar_7.2.gemfile +++ b/gemfiles/ar_7.2.gemfile @@ -6,6 +6,12 @@ gem "rspec" gem "appraisal" gem "rubocop" gem "rubocop-md" +gem "dotenv", require: "dotenv/load" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "sqlite3" +gem "trilogy" gem "activerecord", "~> 7.2.0" gemspec path: "../" diff --git a/integration_test/spec/spec_helper.rb b/integration_test/spec/spec_helper.rb deleted file mode 100644 index c9870e1..0000000 --- a/integration_test/spec/spec_helper.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'arproxy' -require 'active_record' -require 'dotenv/load' -require_relative './shared_examples/custom_proxies' -require_relative './shared_examples/active_record_functions' - -Arproxy.logger.level = Logger::WARN unless ENV['DEBUG'] - -def ar_version - "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}" -end - -def cleanup_activerecord - ActiveRecord::Base.connection.close - ActiveRecord::Base.connection.clear_cache! - ActiveRecord::Base.descendants.each(&:reset_column_information) - ActiveRecord::Base.connection.schema_cache.clear! -end diff --git a/integration_test/spec/arproxy/plugin/query_logger.rb b/spec/arproxy/plugin/query_logger.rb similarity index 100% rename from integration_test/spec/arproxy/plugin/query_logger.rb rename to spec/arproxy/plugin/query_logger.rb diff --git a/spec/arproxy_spec.rb b/spec/arproxy_spec.rb index 8dedb13..474f01e 100644 --- a/spec/arproxy_spec.rb +++ b/spec/arproxy_spec.rb @@ -24,6 +24,8 @@ def execute(sql, name) module ::ActiveRecord module ConnectionAdapters class DummyAdapter + ADAPTER_NAME = 'dummy' + def execute(sql, name = nil) { sql: sql, name: name } end @@ -145,7 +147,7 @@ def execute(sql, name = nil) it { should == { sql: 'SQL_PLUGIN', name: 'NAME_PLUGIN', options: [:option_a, :option_b] } } end - context 'ProxyChain thread-safety' do + xcontext 'ProxyChain thread-safety' do class ProxyWithConnectionId < Arproxy::Base def execute(sql, name) sleep 0.1 diff --git a/spec/arproxy/config_spec.rb b/spec/config_spec.rb similarity index 100% rename from spec/arproxy/config_spec.rb rename to spec/config_spec.rb diff --git a/integration_test/spec/mysql2_spec.rb b/spec/integration/mysql2_spec.rb similarity index 95% rename from integration_test/spec/mysql2_spec.rb rename to spec/integration/mysql2_spec.rb index 39afc84..2c422ce 100644 --- a/integration_test/spec/mysql2_spec.rb +++ b/spec/integration/mysql2_spec.rb @@ -1,4 +1,4 @@ -require_relative 'spec_helper' +require_relative '../spec_helper' context "MySQL (AR#{ar_version})" do before(:all) do diff --git a/integration_test/spec/postgresql_spec.rb b/spec/integration/postgresql_spec.rb similarity index 95% rename from integration_test/spec/postgresql_spec.rb rename to spec/integration/postgresql_spec.rb index 0afc747..bc6d63e 100644 --- a/integration_test/spec/postgresql_spec.rb +++ b/spec/integration/postgresql_spec.rb @@ -1,4 +1,4 @@ -require_relative 'spec_helper' +require_relative '../spec_helper' context "PostgreSQL (AR#{ar_version})" do before(:all) do diff --git a/integration_test/spec/shared_examples/active_record_functions.rb b/spec/integration/shared_examples/active_record_functions.rb similarity index 100% rename from integration_test/spec/shared_examples/active_record_functions.rb rename to spec/integration/shared_examples/active_record_functions.rb diff --git a/integration_test/spec/shared_examples/custom_proxies.rb b/spec/integration/shared_examples/custom_proxies.rb similarity index 100% rename from integration_test/spec/shared_examples/custom_proxies.rb rename to spec/integration/shared_examples/custom_proxies.rb diff --git a/integration_test/spec/sqlite3_spec.rb b/spec/integration/sqlite3_spec.rb similarity index 94% rename from integration_test/spec/sqlite3_spec.rb rename to spec/integration/sqlite3_spec.rb index 813927e..5ae82f6 100644 --- a/integration_test/spec/sqlite3_spec.rb +++ b/spec/integration/sqlite3_spec.rb @@ -1,4 +1,4 @@ -require_relative 'spec_helper' +require_relative '../spec_helper' context "SQLite3 (AR#{ar_version})" do before(:all) do diff --git a/integration_test/spec/sqlserver_spec.rb b/spec/integration/sqlserver_spec.rb similarity index 96% rename from integration_test/spec/sqlserver_spec.rb rename to spec/integration/sqlserver_spec.rb index 2d14a14..3b6ea09 100644 --- a/integration_test/spec/sqlserver_spec.rb +++ b/spec/integration/sqlserver_spec.rb @@ -1,4 +1,4 @@ -require_relative 'spec_helper' +require_relative '../spec_helper' context "SQLServer (AR#{ar_version})" do before(:all) do diff --git a/integration_test/spec/trilogy_spec.rb b/spec/integration/trilogy_spec.rb similarity index 95% rename from integration_test/spec/trilogy_spec.rb rename to spec/integration/trilogy_spec.rb index 3255a41..441528b 100644 --- a/integration_test/spec/trilogy_spec.rb +++ b/spec/integration/trilogy_spec.rb @@ -1,4 +1,4 @@ -require_relative 'spec_helper' +require_relative '../spec_helper' context "Trilogy (AR#{ar_version})", if: ActiveRecord.version >= '7.1' do before(:all) do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6727b44..5e135e3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1 +1,18 @@ -require File.expand_path('../../lib/arproxy', __FILE__) +require 'arproxy' +require 'active_record' +require 'dotenv/load' +require_relative './integration/shared_examples/custom_proxies' +require_relative './integration/shared_examples/active_record_functions' + +Arproxy.logger.level = Logger::WARN unless ENV['DEBUG'] + +def ar_version + "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}" +end + +def cleanup_activerecord + ActiveRecord::Base.connection.close + ActiveRecord::Base.connection.clear_cache! + ActiveRecord::Base.descendants.each(&:reset_column_information) + ActiveRecord::Base.connection.schema_cache.clear! +end From 071e59fed78cbbdf00720b252c285f74b9bc35dc Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 14:55:05 +0900 Subject: [PATCH 40/68] Fix for unit tests --- lib/arproxy.rb | 103 +++++++++++++----------- lib/arproxy/connection_adapter_patch.rb | 55 +++++++------ lib/arproxy/proxy_chain.rb | 6 +- spec/arproxy/plugin/test.rb | 13 --- spec/arproxy/plugin/test_plugin.rb | 13 +++ spec/arproxy_spec.rb | 61 +++++++++----- 6 files changed, 146 insertions(+), 105 deletions(-) delete mode 100644 spec/arproxy/plugin/test.rb create mode 100644 spec/arproxy/plugin/test_plugin.rb diff --git a/lib/arproxy.rb b/lib/arproxy.rb index 694933b..d872bc7 100644 --- a/lib/arproxy.rb +++ b/lib/arproxy.rb @@ -6,69 +6,76 @@ require 'arproxy/plugin' module Arproxy - @config = @enabled = nil + @config = nil + @enabled = nil + @patch = nil module_function - def clear_configuration - @config = Config.new - end - - def configure - clear_configuration unless @config - yield @config - end - - def enable! - if enable? - Arproxy.logger.warn 'Arproxy has been already enabled' - return + def clear_configuration + @config = nil end - unless @config - raise Arproxy::Error, 'Arproxy should be configured' + def configure + @config = Config.new + yield @config end - @proxy_chain = ProxyChain.new @config - @proxy_chain.enable! + def enable! + if enable? + Arproxy.logger.warn 'Arproxy has been already enabled' + return + end - @enabled = true - end + unless @config + raise Arproxy::Error, 'Arproxy has not been configured' + end - def disable! - unless enable? - Arproxy.logger.warn 'Arproxy is not enabled yet' - return - end + @patch = ConnectionAdapterPatch.new(@config.adapter_class) + @proxy_chain = ProxyChain.new(@config, @patch) + @proxy_chain.enable! - if @proxy_chain - @proxy_chain.disable! - @proxy_chain = nil + @enabled = true end - clear_configuration + def disable! + unless enable? + Arproxy.logger.warn 'Arproxy is not enabled yet' + return + end - @enabled = false - end + if @proxy_chain + @proxy_chain.disable! + @proxy_chain = nil + end - def enable? - !!@enabled - end + clear_configuration - def reenable! - if enable? - @proxy_chain.reenable! - else - enable! + @enabled = false end - end - def logger - @logger ||= @config && @config.logger || - defined?(::Rails) && ::Rails.logger || - ::Logger.new(STDOUT) - end + def enable? + !!@enabled + end - def proxy_chain - @proxy_chain - end + def reenable! + if enable? + @proxy_chain.reenable! + else + enable! + end + end + + def logger + @logger ||= @config && @config.logger || + defined?(::Rails) && ::Rails.logger || + ::Logger.new(STDOUT) + end + + def proxy_chain + @proxy_chain + end + + def connection_adapter_patch + @patch + end end diff --git a/lib/arproxy/connection_adapter_patch.rb b/lib/arproxy/connection_adapter_patch.rb index 736773e..d303153 100644 --- a/lib/arproxy/connection_adapter_patch.rb +++ b/lib/arproxy/connection_adapter_patch.rb @@ -7,32 +7,41 @@ def initialize(adapter_class) @applied_patches = Set.new end + def self.register_patches(adapter_name, patches: [], binds_patches: []) + @@patches ||= {} + @@patches[adapter_name] = { + patches: patches, + binds_patches: binds_patches + } + end + + if ActiveRecord.version >= '7.0' + register_patches('Mysql2', patches: [:raw_execute], binds_patches: []) + else + register_patches('Mysql2', patches: [:execute], binds_patches: []) + end + + if ActiveRecord.version >= '7.1' + register_patches('PostgreSQL', patches: [:raw_execute], binds_patches: [:exec_no_cache, :exec_cache]) + register_patches('SQLServer', patches: [:raw_execute], binds_patches: [:internal_exec_query]) + register_patches('SQLite', patches: [:raw_execute], binds_patches: [:internal_exec_query]) + else + register_patches('PostgreSQL', patches: [:execute], binds_patches: [:exec_no_cache, :exec_cache]) + register_patches('SQLServer', patches: [:execute], binds_patches: [:exec_query]) + register_patches('SQLite', patches: [:execute], binds_patches: [:exec_query]) + end + def enable! - case adapter_class::ADAPTER_NAME - when 'Mysql2', 'Trilogy' - if ActiveRecord.version >= '7.0' - apply_patch :raw_execute - else - apply_patch :execute - end - when 'PostgreSQL' - if ActiveRecord.version >= '7.1' - apply_patch :raw_execute - else - apply_patch :execute + patches = @@patches[adapter_class::ADAPTER_NAME] + if patches + patches[:patches]&.each do |patch| + apply_patch patch end - apply_patch_binds :exec_no_cache - apply_patch_binds :exec_cache - when 'SQLServer', 'SQLite' - if ActiveRecord.version >= '7.1' - apply_patch :raw_execute - apply_patch_binds :internal_exec_query - else - apply_patch :execute - apply_patch_binds :exec_query + patches[:binds_patches]&.each do |binds_patch| + apply_patch_binds binds_patch end else - raise Arproxy::Error, "Unexpected connection adapter: #{adapter_class&.name}" + raise Arproxy::Error, "Unexpected connection adapter: patches not registered for #{adapter_class&.name}" end ::Arproxy.logger.debug("Arproxy: Enabled (#{adapter_class::ADAPTER_NAME})") end @@ -40,7 +49,7 @@ def enable! def disable! @applied_patches.dup.each do |target_method| adapter_class.class_eval do - if respond_to?(:"#{target_method}_with_arproxy") + if instance_methods.include?(:"#{target_method}_with_arproxy") alias_method target_method, :"#{target_method}_without_arproxy" remove_method :"#{target_method}_with_arproxy" end diff --git a/lib/arproxy/proxy_chain.rb b/lib/arproxy/proxy_chain.rb index 7fcd62a..a7b0462 100644 --- a/lib/arproxy/proxy_chain.rb +++ b/lib/arproxy/proxy_chain.rb @@ -3,16 +3,16 @@ module Arproxy class ProxyChain - attr_reader :head, :tail + attr_reader :head, :tail, :patch - def initialize(config) + def initialize(config, patch) @config = config + @patch = patch setup end def setup @tail = ChainTail.new self - @patch = ConnectionAdapterPatch.new(@config.adapter_class) @head = @config.proxies.reverse.inject(@tail) do |next_proxy, proxy_config| cls, options = proxy_config proxy = cls.new(*options) diff --git a/spec/arproxy/plugin/test.rb b/spec/arproxy/plugin/test.rb deleted file mode 100644 index f066faf..0000000 --- a/spec/arproxy/plugin/test.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Arproxy::Plugin - class Test < Arproxy::Base - Arproxy::Plugin.register(:test, self) - - def initialize(*options) - @options = options - end - - def execute(sql, name=nil) - { sql: "#{sql}_PLUGIN", name: "#{name}_PLUGIN", options: @options } - end - end -end diff --git a/spec/arproxy/plugin/test_plugin.rb b/spec/arproxy/plugin/test_plugin.rb new file mode 100644 index 0000000..7cef06e --- /dev/null +++ b/spec/arproxy/plugin/test_plugin.rb @@ -0,0 +1,13 @@ +module Arproxy::Plugin + class TestPlugin < Arproxy::Base + Arproxy::Plugin.register(:test_plugin, self) + + def initialize(*options) + @options = options + end + + def execute(sql, name=nil) + super("#{sql} /* options: #{@options.inspect} */", "#{name}_PLUGIN") + end + end +end diff --git a/spec/arproxy_spec.rb b/spec/arproxy_spec.rb index 474f01e..10ac81d 100644 --- a/spec/arproxy_spec.rb +++ b/spec/arproxy_spec.rb @@ -24,17 +24,21 @@ def execute(sql, name) module ::ActiveRecord module ConnectionAdapters class DummyAdapter - ADAPTER_NAME = 'dummy' + ADAPTER_NAME = 'Dummy' - def execute(sql, name = nil) - { sql: sql, name: name } + def execute1(sql, name = nil, **kwargs) + { sql: sql, name: name, kwargs: kwargs } + end + + def execute2(sql, name = nil, binds = [], **kwargs) + { sql: sql, name: name, binds: binds, kwargs: kwargs } end end + Arproxy::ConnectionAdapterPatch.register_patches('Dummy', patches: [:execute1], binds_patches: [:execute2]) end end let(:connection) { ::ActiveRecord::ConnectionAdapters::DummyAdapter.new } - subject { connection.execute 'SQL', 'NAME' } after(:each) do Arproxy.disable! end @@ -49,7 +53,18 @@ def execute(sql, name = nil) Arproxy.enable! end - it { should == { sql: 'SQL_A', name: 'NAME_A' } } + it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL_A', name: 'NAME_A', kwargs: {} }) } + it { expect(connection.execute1('SQL', 'NAME', a: 1, b: 2)).to eq({ sql: 'SQL_A', name: 'NAME_A', kwargs: { a: 1, b: 2 } }) } + + it { expect(connection.execute2('SQL', 'NAME')).to eq({ sql: 'SQL_A', name: 'NAME_A', binds: [], kwargs: {} }) } + + it do + expect( + connection.execute2('SQL', 'NAME', [:x, :y], a: 1, b: 2) + ).to eq( + { sql: 'SQL_A', name: 'NAME_A', binds: [:x, :y], kwargs: { a: 1, b: 2 } } + ) + end end context 'with 2 proxies' do @@ -63,7 +78,7 @@ def execute(sql, name = nil) Arproxy.enable! end - it { should == { sql: 'SQL_A_B', name: 'NAME_A_B' } } + it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL_A_B', name: 'NAME_A_B', kwargs: {} }) } end context 'with 2 proxies which have an option' do @@ -77,7 +92,7 @@ def execute(sql, name = nil) Arproxy.enable! end - it { should == { sql: 'SQL_A_B1', name: 'NAME_A_B1' } } + it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL_A_B1', name: 'NAME_A_B1', kwargs: {} }) } end context do @@ -94,7 +109,7 @@ def execute(sql, name = nil) Arproxy.enable! Arproxy.disable! end - it { should == { sql: 'SQL', name: 'NAME' } } + it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL', name: 'NAME', kwargs: {} }) } end context 'enable -> enable' do @@ -102,7 +117,7 @@ def execute(sql, name = nil) Arproxy.enable! Arproxy.enable! end - it { should == { sql: 'SQL_A', name: 'NAME_A' } } + it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL_A', name: 'NAME_A', kwargs: {} }) } end context 'enable -> disable -> disable' do @@ -111,26 +126,30 @@ def execute(sql, name = nil) Arproxy.disable! Arproxy.disable! end - it { should == { sql: 'SQL', name: 'NAME' } } + it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL', name: 'NAME', kwargs: {} }) } end context 'enable -> disable -> enable' do before do Arproxy.enable! Arproxy.disable! - Arproxy.enable! end - it { should == { sql: 'SQL_A', name: 'NAME_A' } } + it do + expect { + Arproxy.enable! + }.to raise_error(Arproxy::Error, /Arproxy has not been configured/) + end end context 're-configure' do before do Arproxy.configure do |config| + config.adapter = 'dummy' config.use ProxyB end Arproxy.enable! end - it { should == { sql: 'SQL_A_B', name: 'NAME_A_B' } } + it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL_A_B', name: 'NAME_A_B', kwargs: {} }) } end end @@ -139,15 +158,21 @@ def execute(sql, name = nil) Arproxy.clear_configuration Arproxy.configure do |config| config.adapter = 'dummy' - config.plugin :test, :option_a, :option_b + config.plugin :test_plugin, :option_a, :option_b end Arproxy.enable! end - it { should == { sql: 'SQL_PLUGIN', name: 'NAME_PLUGIN', options: [:option_a, :option_b] } } + it do + expect( + connection.execute1('SQL', 'NAME') + ).to eq( + { sql: 'SQL /* options: [:option_a, :option_b] */', name: 'NAME_PLUGIN', kwargs: {} } + ) + end end - xcontext 'ProxyChain thread-safety' do + context 'ProxyChain thread-safety' do class ProxyWithConnectionId < Arproxy::Base def execute(sql, name) sleep 0.1 @@ -165,8 +190,8 @@ def execute(sql, name) end context 'with two threads' do - let!(:thr1) { Thread.new { connection.dup.execute 'SELECT 1' } } - let!(:thr2) { Thread.new { connection.dup.execute 'SELECT 1' } } + let!(:thr1) { Thread.new { connection.dup.execute1 'SELECT 1' } } + let!(:thr2) { Thread.new { connection.dup.execute1 'SELECT 1' } } it { expect(thr1.value).not_to eq(thr2.value) } end From 33e3cc7a0bf582a1887ea9ca113a062f1366f0f2 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 14:57:45 +0900 Subject: [PATCH 41/68] Bump version to v1.0.0 --- lib/arproxy/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/arproxy/version.rb b/lib/arproxy/version.rb index f6ea29d..bb9c14d 100644 --- a/lib/arproxy/version.rb +++ b/lib/arproxy/version.rb @@ -1,3 +1,3 @@ module Arproxy - VERSION = '0.2.9' + VERSION = '1.0.0' end From 619714072b620daa909c9c9319837ba50b0f5449 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 15:04:11 +0900 Subject: [PATCH 42/68] Add missing Trilogy --- lib/arproxy/connection_adapter_patch.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/arproxy/connection_adapter_patch.rb b/lib/arproxy/connection_adapter_patch.rb index d303153..76395f3 100644 --- a/lib/arproxy/connection_adapter_patch.rb +++ b/lib/arproxy/connection_adapter_patch.rb @@ -17,8 +17,10 @@ def self.register_patches(adapter_name, patches: [], binds_patches: []) if ActiveRecord.version >= '7.0' register_patches('Mysql2', patches: [:raw_execute], binds_patches: []) + register_patches('Trilogy', patches: [:raw_execute], binds_patches: []) else register_patches('Mysql2', patches: [:execute], binds_patches: []) + register_patches('Trilogy', patches: [:raw_execute], binds_patches: []) end if ActiveRecord.version >= '7.1' From b8ef2c3dc17b7b017e40da8472762f1da011d114 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 15:32:58 +0900 Subject: [PATCH 43/68] Fix bugs in clear configuration --- lib/arproxy.rb | 6 ++---- lib/arproxy/connection_adapter_patch.rb | 1 + spec/arproxy_spec.rb | 16 +++++++++++++--- spec/integration/mysql2_spec.rb | 1 + spec/integration/postgresql_spec.rb | 1 + spec/integration/sqlite3_spec.rb | 1 + spec/integration/sqlserver_spec.rb | 1 + spec/integration/trilogy_spec.rb | 1 + 8 files changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/arproxy.rb b/lib/arproxy.rb index d872bc7..e757ac9 100644 --- a/lib/arproxy.rb +++ b/lib/arproxy.rb @@ -16,13 +16,13 @@ def clear_configuration end def configure - @config = Config.new + @config ||= Config.new yield @config end def enable! if enable? - Arproxy.logger.warn 'Arproxy has been already enabled' + Arproxy.logger.warn 'Arproxy has already been enabled' return end @@ -48,8 +48,6 @@ def disable! @proxy_chain = nil end - clear_configuration - @enabled = false end diff --git a/lib/arproxy/connection_adapter_patch.rb b/lib/arproxy/connection_adapter_patch.rb index 76395f3..da85e64 100644 --- a/lib/arproxy/connection_adapter_patch.rb +++ b/lib/arproxy/connection_adapter_patch.rb @@ -65,6 +65,7 @@ def disable! def apply_patch(target_method) return if @applied_patches.include?(target_method) adapter_class.class_eval do + break if instance_methods.include?(:"#{target_method}_with_arproxy") define_method("#{target_method}_with_arproxy") do |sql, name=nil, **kwargs| ::Arproxy.proxy_chain.connection = self _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) diff --git a/spec/arproxy_spec.rb b/spec/arproxy_spec.rb index 10ac81d..ae9ce6a 100644 --- a/spec/arproxy_spec.rb +++ b/spec/arproxy_spec.rb @@ -41,6 +41,7 @@ def execute2(sql, name = nil, binds = [], **kwargs) let(:connection) { ::ActiveRecord::ConnectionAdapters::DummyAdapter.new } after(:each) do Arproxy.disable! + Arproxy.clear_configuration end context 'with a proxy' do @@ -129,10 +130,9 @@ def execute2(sql, name = nil, binds = [], **kwargs) it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL', name: 'NAME', kwargs: {} }) } end - context 'enable -> disable -> enable' do + context 'clear_configuration -> enable' do before do - Arproxy.enable! - Arproxy.disable! + Arproxy.clear_configuration end it do expect { @@ -141,6 +141,16 @@ def execute2(sql, name = nil, binds = [], **kwargs) end end + + context 'enable -> disable -> enable' do + before do + Arproxy.enable! + Arproxy.disable! + Arproxy.enable! + end + it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL_A', name: 'NAME_A', kwargs: {} }) } + end + context 're-configure' do before do Arproxy.configure do |config| diff --git a/spec/integration/mysql2_spec.rb b/spec/integration/mysql2_spec.rb index 2c422ce..e54eb22 100644 --- a/spec/integration/mysql2_spec.rb +++ b/spec/integration/mysql2_spec.rb @@ -22,6 +22,7 @@ after(:all) do cleanup_activerecord Arproxy.disable! + Arproxy.clear_configuration end it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' diff --git a/spec/integration/postgresql_spec.rb b/spec/integration/postgresql_spec.rb index bc6d63e..86f42ed 100644 --- a/spec/integration/postgresql_spec.rb +++ b/spec/integration/postgresql_spec.rb @@ -22,6 +22,7 @@ after(:all) do cleanup_activerecord Arproxy.disable! + Arproxy.clear_configuration end it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' diff --git a/spec/integration/sqlite3_spec.rb b/spec/integration/sqlite3_spec.rb index 5ae82f6..e47684f 100644 --- a/spec/integration/sqlite3_spec.rb +++ b/spec/integration/sqlite3_spec.rb @@ -18,6 +18,7 @@ after(:all) do cleanup_activerecord Arproxy.disable! + Arproxy.clear_configuration end it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' diff --git a/spec/integration/sqlserver_spec.rb b/spec/integration/sqlserver_spec.rb index 3b6ea09..cde672c 100644 --- a/spec/integration/sqlserver_spec.rb +++ b/spec/integration/sqlserver_spec.rb @@ -29,6 +29,7 @@ after(:all) do cleanup_activerecord Arproxy.disable! + Arproxy.clear_configuration end it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' diff --git a/spec/integration/trilogy_spec.rb b/spec/integration/trilogy_spec.rb index 441528b..ee6fb8a 100644 --- a/spec/integration/trilogy_spec.rb +++ b/spec/integration/trilogy_spec.rb @@ -22,6 +22,7 @@ after(:all) do cleanup_activerecord Arproxy.disable! + Arproxy.clear_configuration end it_behaves_like 'Arproxy does not break the original ActiveRecord functionality' From 50f10468871ef46ad897f143014e82088359405d Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 15:39:08 +0900 Subject: [PATCH 44/68] Fix github actions --- ...gration_test.yml => integration_tests.yml} | 7 ++-- .github/workflows/ruby.yml | 37 ------------------ .github/workflows/unit_tests.yml | 38 +++++++++++++++++++ 3 files changed, 41 insertions(+), 41 deletions(-) rename .github/workflows/{integration_test.yml => integration_tests.yml} (89%) delete mode 100644 .github/workflows/ruby.yml create mode 100644 .github/workflows/unit_tests.yml diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_tests.yml similarity index 89% rename from .github/workflows/integration_test.yml rename to .github/workflows/integration_tests.yml index db09cc2..b80c68d 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_tests.yml @@ -1,4 +1,4 @@ -name: Integration Test +name: Integration tests on: push: @@ -41,9 +41,8 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ env.RUBY_VERSION }} + bundler-cache: true - name: Start DB run: docker compose up -d - - name: Run bundle install - run: bundle install - name: Run integration test - run: bundle exec rspec + run: bundle exec rspec spec/integration/*_spec.rb diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml deleted file mode 100644 index 5f06868..0000000 --- a/.github/workflows/ruby.yml +++ /dev/null @@ -1,37 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake -# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby - -name: Ruby - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test: - - runs-on: ubuntu-latest - strategy: - matrix: - ruby-version: ['3.2', '3.3'] - gemfile: ['ar_7.1', 'ar_7.2'] - - steps: - - uses: actions/checkout@v2 - - name: Set up Ruby - # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, - # change this to (see https://github.com/ruby/setup-ruby#versioning): - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby-version }} - bundler-cache: true # runs 'bundle install' and caches installed gems automatically - - name: Run tests with ${{ matrix.gemfile }}.gemfile on ruby-${{ matrix.ruby-version }} - run: bundle install && bundle exec rake spec - env: - BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000..7c2c7a3 --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,38 @@ +name: Unit tests + +on: + push: + branches: + - main + - v1 + pull_request: + branches: + - main + - v1 + +jobs: + test: + + runs-on: ubuntu-latest + strategy: + matrix: + ruby-version: + - 3.2 + - 3.3 + gemfile: + - ar_6.1 + - ar_7.0 + - ar_7.1 + - ar_7.2 + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + - name: Run tests with ${{ matrix.gemfile }}.gemfile on ruby-${{ matrix.ruby-version }} + run: bundle exec rspec spec/*_spec.rb + env: + BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile From 02b8d40ab6a8d28989671f7eed4cf8333c1918d6 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 15:56:37 +0900 Subject: [PATCH 45/68] Update gemfiles --- .github/workflows/integration_tests.yml | 4 +--- .github/workflows/rubocop.yml | 4 +--- .github/workflows/unit_tests.yml | 9 +-------- Gemfile | 7 +------ gemfiles/ar_4.2.gemfile | 7 ------- gemfiles/ar_5.2.gemfile | 7 ------- gemfiles/ar_6.1.gemfile | 8 ++------ gemfiles/ar_7.0.gemfile | 8 ++------ gemfiles/ar_7.1.gemfile | 7 +------ gemfiles/ar_7.2.gemfile | 7 +------ integration_test/Appraisals | 1 - integration_test/Gemfile | 11 ----------- integration_test/gemfiles/ar_6.1.gemfile | 17 ----------------- integration_test/gemfiles/ar_7.0.gemfile | 17 ----------------- integration_test/gemfiles/ar_7.1.gemfile | 14 -------------- integration_test/gemfiles/ar_7.2.gemfile | 14 -------------- 16 files changed, 10 insertions(+), 132 deletions(-) delete mode 100644 gemfiles/ar_4.2.gemfile delete mode 100644 gemfiles/ar_5.2.gemfile delete mode 120000 integration_test/Appraisals delete mode 100644 integration_test/Gemfile delete mode 100644 integration_test/gemfiles/ar_6.1.gemfile delete mode 100644 integration_test/gemfiles/ar_7.0.gemfile delete mode 100644 integration_test/gemfiles/ar_7.1.gemfile delete mode 100644 integration_test/gemfiles/ar_7.2.gemfile diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index b80c68d..6a72a2a 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -16,9 +16,7 @@ env: jobs: integration_test: runs-on: ubuntu-latest - defaults: - run: - working-directory: ./integration_test + env: BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile MYSQL_HOST: 127.0.0.1 diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 1ce1639..dcf84a7 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -22,9 +22,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: 3.3 - - - name: Install dependencies - run: bundle install + bundler-cache: true - name: Run RuboCop run: bundle exec rubocop diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 7c2c7a3..75c8434 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -19,11 +19,6 @@ jobs: ruby-version: - 3.2 - 3.3 - gemfile: - - ar_6.1 - - ar_7.0 - - ar_7.1 - - ar_7.2 steps: - uses: actions/checkout@v2 @@ -32,7 +27,5 @@ jobs: with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - - name: Run tests with ${{ matrix.gemfile }}.gemfile on ruby-${{ matrix.ruby-version }} + - name: Run unit tests on ruby-${{ matrix.ruby-version }} run: bundle exec rspec spec/*_spec.rb - env: - BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile diff --git a/Gemfile b/Gemfile index 3e06a76..374e6cf 100644 --- a/Gemfile +++ b/Gemfile @@ -4,11 +4,6 @@ gemspec gem 'rspec' gem 'appraisal' +gem 'dotenv', require: 'dotenv/load' gem 'rubocop' gem 'rubocop-md' -gem 'dotenv', require: 'dotenv/load' -gem 'mysql2' -gem 'pg' -gem 'activerecord-sqlserver-adapter' -gem 'sqlite3' -gem 'trilogy' diff --git a/gemfiles/ar_4.2.gemfile b/gemfiles/ar_4.2.gemfile deleted file mode 100644 index 37f20bc..0000000 --- a/gemfiles/ar_4.2.gemfile +++ /dev/null @@ -1,7 +0,0 @@ -# This file was generated by Appraisal - -source 'https://rubygems.org' - -gem 'activerecord', '~> 4.2.0' - -gemspec path: '../' diff --git a/gemfiles/ar_5.2.gemfile b/gemfiles/ar_5.2.gemfile deleted file mode 100644 index aaef0a9..0000000 --- a/gemfiles/ar_5.2.gemfile +++ /dev/null @@ -1,7 +0,0 @@ -# This file was generated by Appraisal - -source 'https://rubygems.org' - -gem 'activerecord', '~> 5.2.0' - -gemspec path: '../' diff --git a/gemfiles/ar_6.1.gemfile b/gemfiles/ar_6.1.gemfile index 3a66ce6..2886fa3 100644 --- a/gemfiles/ar_6.1.gemfile +++ b/gemfiles/ar_6.1.gemfile @@ -4,15 +4,11 @@ source "https://rubygems.org" gem "rspec" gem "appraisal" +gem "dotenv", require: "dotenv/load" gem "rubocop" gem "rubocop-md" -gem "dotenv", require: "dotenv/load" -gem "mysql2" -gem "pg" -gem "activerecord-sqlserver-adapter" -gem "sqlite3", "~> 1.4" -gem "trilogy" gem "activerecord", "~> 6.1.0" +gem "sqlite3", "~> 1.4" gem "bigdecimal" gem "base64" gem "mutex_m" diff --git a/gemfiles/ar_7.0.gemfile b/gemfiles/ar_7.0.gemfile index 0820eb6..13fabc1 100644 --- a/gemfiles/ar_7.0.gemfile +++ b/gemfiles/ar_7.0.gemfile @@ -4,15 +4,11 @@ source "https://rubygems.org" gem "rspec" gem "appraisal" +gem "dotenv", require: "dotenv/load" gem "rubocop" gem "rubocop-md" -gem "dotenv", require: "dotenv/load" -gem "mysql2" -gem "pg" -gem "activerecord-sqlserver-adapter" -gem "sqlite3", "~> 1.4" -gem "trilogy" gem "activerecord", "~> 7.0.0" +gem "sqlite3", "~> 1.4" gem "bigdecimal" gem "base64" gem "mutex_m" diff --git a/gemfiles/ar_7.1.gemfile b/gemfiles/ar_7.1.gemfile index c11fb53..07d323e 100644 --- a/gemfiles/ar_7.1.gemfile +++ b/gemfiles/ar_7.1.gemfile @@ -4,14 +4,9 @@ source "https://rubygems.org" gem "rspec" gem "appraisal" +gem "dotenv", require: "dotenv/load" gem "rubocop" gem "rubocop-md" -gem "dotenv", require: "dotenv/load" -gem "mysql2" -gem "pg" -gem "activerecord-sqlserver-adapter" -gem "sqlite3" -gem "trilogy" gem "activerecord", "~> 7.1.0" gemspec path: "../" diff --git a/gemfiles/ar_7.2.gemfile b/gemfiles/ar_7.2.gemfile index 10babe6..80db508 100644 --- a/gemfiles/ar_7.2.gemfile +++ b/gemfiles/ar_7.2.gemfile @@ -4,14 +4,9 @@ source "https://rubygems.org" gem "rspec" gem "appraisal" +gem "dotenv", require: "dotenv/load" gem "rubocop" gem "rubocop-md" -gem "dotenv", require: "dotenv/load" -gem "mysql2" -gem "pg" -gem "activerecord-sqlserver-adapter" -gem "sqlite3" -gem "trilogy" gem "activerecord", "~> 7.2.0" gemspec path: "../" diff --git a/integration_test/Appraisals b/integration_test/Appraisals deleted file mode 120000 index 526794e..0000000 --- a/integration_test/Appraisals +++ /dev/null @@ -1 +0,0 @@ -../Appraisals \ No newline at end of file diff --git a/integration_test/Gemfile b/integration_test/Gemfile deleted file mode 100644 index 124d39c..0000000 --- a/integration_test/Gemfile +++ /dev/null @@ -1,11 +0,0 @@ -source 'https://rubygems.org' - -gem 'arproxy', path: '..' -gem 'rspec' -gem 'appraisal' -gem 'dotenv', require: 'dotenv/load' -gem 'mysql2' -gem 'pg' -gem 'activerecord-sqlserver-adapter' -gem 'sqlite3' -gem 'trilogy' diff --git a/integration_test/gemfiles/ar_6.1.gemfile b/integration_test/gemfiles/ar_6.1.gemfile deleted file mode 100644 index 1082012..0000000 --- a/integration_test/gemfiles/ar_6.1.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "arproxy", path: "../.." -gem "rspec" -gem "appraisal" -gem "dotenv", require: "dotenv/load" -gem "mysql2" -gem "pg" -gem "activerecord-sqlserver-adapter" -gem "sqlite3", "~> 1.4" -gem "trilogy" -gem "activerecord", "~> 6.1.0" -gem "bigdecimal" -gem "base64" -gem "mutex_m" diff --git a/integration_test/gemfiles/ar_7.0.gemfile b/integration_test/gemfiles/ar_7.0.gemfile deleted file mode 100644 index 43a1952..0000000 --- a/integration_test/gemfiles/ar_7.0.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "arproxy", path: "../.." -gem "rspec" -gem "appraisal" -gem "dotenv", require: "dotenv/load" -gem "mysql2" -gem "pg" -gem "activerecord-sqlserver-adapter" -gem "sqlite3", "~> 1.4" -gem "trilogy" -gem "activerecord", "~> 7.0.0" -gem "bigdecimal" -gem "base64" -gem "mutex_m" diff --git a/integration_test/gemfiles/ar_7.1.gemfile b/integration_test/gemfiles/ar_7.1.gemfile deleted file mode 100644 index daa221e..0000000 --- a/integration_test/gemfiles/ar_7.1.gemfile +++ /dev/null @@ -1,14 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "arproxy", path: "../.." -gem "rspec" -gem "appraisal" -gem "dotenv", require: "dotenv/load" -gem "mysql2" -gem "pg" -gem "activerecord-sqlserver-adapter" -gem "sqlite3" -gem "trilogy" -gem "activerecord", "~> 7.1.0" diff --git a/integration_test/gemfiles/ar_7.2.gemfile b/integration_test/gemfiles/ar_7.2.gemfile deleted file mode 100644 index 68f1c9b..0000000 --- a/integration_test/gemfiles/ar_7.2.gemfile +++ /dev/null @@ -1,14 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "arproxy", path: "../.." -gem "rspec" -gem "appraisal" -gem "dotenv", require: "dotenv/load" -gem "mysql2" -gem "pg" -gem "activerecord-sqlserver-adapter" -gem "sqlite3" -gem "trilogy" -gem "activerecord", "~> 7.2.0" From a4384058a40b89d3449e29c502512f4c8e3e8321 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 16:04:44 +0900 Subject: [PATCH 46/68] Update appraisals --- Appraisals | 18 ++++++++++++++++++ gemfiles/ar_6.1.gemfile | 4 ++++ gemfiles/ar_7.0.gemfile | 4 ++++ gemfiles/ar_7.1.gemfile | 5 +++++ gemfiles/ar_7.2.gemfile | 5 +++++ 5 files changed, 36 insertions(+) diff --git a/Appraisals b/Appraisals index fcb61c2..4aa839b 100644 --- a/Appraisals +++ b/Appraisals @@ -1,5 +1,9 @@ appraise 'ar-6.1' do gem 'activerecord', '~> 6.1.0' + gem 'mysql2' + gem 'pg' + gem 'activerecord-sqlserver-adapter' + gem 'trilogy' gem 'sqlite3', '~> 1.4' # required to suppress warnings @@ -10,6 +14,10 @@ end appraise 'ar-7.0' do gem 'activerecord', '~> 7.0.0' + gem 'mysql2' + gem 'pg' + gem 'activerecord-sqlserver-adapter' + gem 'trilogy' gem 'sqlite3', '~> 1.4' # required to suppress warnings @@ -20,8 +28,18 @@ end appraise 'ar-7.1' do gem 'activerecord', '~> 7.1.0' + gem 'mysql2' + gem 'pg' + gem 'activerecord-sqlserver-adapter' + gem 'trilogy' + gem 'sqlite3', '~> 2.0' end appraise 'ar-7.2' do gem 'activerecord', '~> 7.2.0' + gem 'mysql2' + gem 'pg' + gem 'activerecord-sqlserver-adapter' + gem 'trilogy' + gem 'sqlite3', '~> 2.0' end diff --git a/gemfiles/ar_6.1.gemfile b/gemfiles/ar_6.1.gemfile index 2886fa3..063ca75 100644 --- a/gemfiles/ar_6.1.gemfile +++ b/gemfiles/ar_6.1.gemfile @@ -8,6 +8,10 @@ gem "dotenv", require: "dotenv/load" gem "rubocop" gem "rubocop-md" gem "activerecord", "~> 6.1.0" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "trilogy" gem "sqlite3", "~> 1.4" gem "bigdecimal" gem "base64" diff --git a/gemfiles/ar_7.0.gemfile b/gemfiles/ar_7.0.gemfile index 13fabc1..b3fc700 100644 --- a/gemfiles/ar_7.0.gemfile +++ b/gemfiles/ar_7.0.gemfile @@ -8,6 +8,10 @@ gem "dotenv", require: "dotenv/load" gem "rubocop" gem "rubocop-md" gem "activerecord", "~> 7.0.0" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "trilogy" gem "sqlite3", "~> 1.4" gem "bigdecimal" gem "base64" diff --git a/gemfiles/ar_7.1.gemfile b/gemfiles/ar_7.1.gemfile index 07d323e..a86a5a8 100644 --- a/gemfiles/ar_7.1.gemfile +++ b/gemfiles/ar_7.1.gemfile @@ -8,5 +8,10 @@ gem "dotenv", require: "dotenv/load" gem "rubocop" gem "rubocop-md" gem "activerecord", "~> 7.1.0" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "trilogy" +gem "sqlite3", "~> 2.0" gemspec path: "../" diff --git a/gemfiles/ar_7.2.gemfile b/gemfiles/ar_7.2.gemfile index 80db508..d1f14e3 100644 --- a/gemfiles/ar_7.2.gemfile +++ b/gemfiles/ar_7.2.gemfile @@ -8,5 +8,10 @@ gem "dotenv", require: "dotenv/load" gem "rubocop" gem "rubocop-md" gem "activerecord", "~> 7.2.0" +gem "mysql2" +gem "pg" +gem "activerecord-sqlserver-adapter" +gem "trilogy" +gem "sqlite3", "~> 2.0" gemspec path: "../" From 34fa32c058ef6afa0bac0b35fe458ecfe9e83cef Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 16:10:36 +0900 Subject: [PATCH 47/68] Bump actions/checkout version --- .github/workflows/unit_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 75c8434..d80683e 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -21,7 +21,7 @@ jobs: - 3.3 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 81b7c2f9bf23745e940073c9a00be9792dd86df8 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 16:39:57 +0900 Subject: [PATCH 48/68] Ignore vendor/ directory in rubocop --- .rubocop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop.yml b/.rubocop.yml index e88625d..61109a2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -11,6 +11,7 @@ AllCops: Exclude: - '**/tmp/**/*' - '**/*.gemfile' + - 'vendor/**/*' # Prefer &&/|| over and/or. Style/AndOr: From b63cd2ffa570c561a557a373d195cf75cca20c76 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sat, 14 Sep 2024 16:46:18 +0900 Subject: [PATCH 49/68] bundle install --- .github/workflows/integration_tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 6a72a2a..ea7b660 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -39,8 +39,9 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ env.RUBY_VERSION }} - bundler-cache: true - name: Start DB run: docker compose up -d + - name: Install gems + run: bundle install - name: Run integration test run: bundle exec rspec spec/integration/*_spec.rb From a286c29a4acf5b14508ba69e342e29c7afa36797 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 16 Sep 2024 16:12:54 +0900 Subject: [PATCH 50/68] Add support for Ruby 2.7+ --- .github/workflows/unit_tests.yml | 3 +++ lib/arproxy/connection_adapter_patch.rb | 4 ++-- spec/integration/sqlserver_spec.rb | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index d80683e..18df567 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -17,6 +17,9 @@ jobs: strategy: matrix: ruby-version: + - 2.7 + - 3.0 + - 3.1 - 3.2 - 3.3 diff --git a/lib/arproxy/connection_adapter_patch.rb b/lib/arproxy/connection_adapter_patch.rb index da85e64..ca098b6 100644 --- a/lib/arproxy/connection_adapter_patch.rb +++ b/lib/arproxy/connection_adapter_patch.rb @@ -15,7 +15,7 @@ def self.register_patches(adapter_name, patches: [], binds_patches: []) } end - if ActiveRecord.version >= '7.0' + if ActiveRecord.version >= Gem::Version.new('7.0') register_patches('Mysql2', patches: [:raw_execute], binds_patches: []) register_patches('Trilogy', patches: [:raw_execute], binds_patches: []) else @@ -23,7 +23,7 @@ def self.register_patches(adapter_name, patches: [], binds_patches: []) register_patches('Trilogy', patches: [:raw_execute], binds_patches: []) end - if ActiveRecord.version >= '7.1' + if ActiveRecord.version >= Gem::Version.new('7.1') register_patches('PostgreSQL', patches: [:raw_execute], binds_patches: [:exec_no_cache, :exec_cache]) register_patches('SQLServer', patches: [:raw_execute], binds_patches: [:internal_exec_query]) register_patches('SQLite', patches: [:raw_execute], binds_patches: [:internal_exec_query]) diff --git a/spec/integration/sqlserver_spec.rb b/spec/integration/sqlserver_spec.rb index cde672c..57e0b6d 100644 --- a/spec/integration/sqlserver_spec.rb +++ b/spec/integration/sqlserver_spec.rb @@ -2,7 +2,7 @@ context "SQLServer (AR#{ar_version})" do before(:all) do - if ActiveRecord.version >= '7.2' + if ActiveRecord.version >= Gem::Version.new('7.2') ActiveRecord::ConnectionAdapters.register( 'sqlserver', 'ActiveRecord::ConnectionAdapters::SQLServerAdapter', From b290279650539e2aaaecbc7b697dddbb4fb70b10 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 16 Sep 2024 16:43:14 +0900 Subject: [PATCH 51/68] Update README --- README.md | 60 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 231fb27..1d9f4af 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ [![Build Status](https://github.com/cookpad/arproxy/actions/workflows/ruby.yml/badge.svg)](https://github.com/cookpad/arproxy/actions) -## Welcome to Arproxy -Arproxy is a proxy between ActiveRecord and Database adapters. -You can make a custom proxy what analyze and/or modify SQLs before DB adapter executes them. +## Arproxy +Arproxy is a library that can intercept SQL queries executed by ActiveRecord to log them or modify the queries themselves. ## Getting Started -Write your proxy and its configurations in Rails' config/initializers: +Create your custom proxy and add its configuration in your Rails' `config/initializers/` directory: ```ruby class QueryTracer < Arproxy::Base @@ -30,13 +29,20 @@ Then you can see the backtrace of SQLs in the Rails' log. MyTable.where(id: id).limit(1) # => The SQL and the backtrace appear in the log ``` +### What the `name' argument is +In the Rails' log you may see queries like this: +``` +User Load (22.6ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = 'Issei Naruta' +``` +Then `"User Load"` is the `name`. + ## Architecture Without Arproxy: ``` -+-------------------------+ +------------------+ -| ActiveRecord::Base#find |--execute(sql, name)-->| Database Adapter | -+-------------------------+ +------------------+ ++-------------------------+ +------------------+ +| ActiveRecord::Base#find |--SQL-->| Database Adapter | ++-------------------------+ +------------------+ ``` With Arproxy: @@ -50,9 +56,9 @@ end ``` ``` -+-------------------------+ +----------+ +----------+ +------------------+ -| ActiveRecord::Base#find |--execute(sql, name)-->| MyProxy1 |-->| MyProxy2 |-->| Database Adapter | -+-------------------------+ +----------+ +----------+ +------------------+ ++-------------------------+ +----------+ +----------+ +------------------+ +| ActiveRecord::Base#find |--SQL-->| MyProxy1 |-->| MyProxy2 |-->| Database Adapter | ++-------------------------+ +----------+ +----------+ +------------------+ ``` ## Examples @@ -122,16 +128,34 @@ Arproxy.configure do |config| end ``` -## Appendix -### What the `name' argument is -In the Rails' log you may see queries like this: +## Development + +### Setup + ``` -User Load (22.6ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = 'Issei Naruta' +$ git clone https://github.com/cookpad/arproxy.git +$ cd arproxy +$ bundle install +$ bundle exec appraisal install +``` + +### Run test + +To run all tests with all supported versions of ActiveRecord: + +``` +$ docker compose up -d +$ bundle exec appraisal rspec ``` -Then `"User Load"` is the `name`. -## License +To run tests for a specific version of ActiveRecord: + +``` +$ bundle exec appraisal ar_7.1 rspec +or +$ BUNDLE_GEMFILE=gemfiles/ar_7.1.gemfile bundle exec rspec +``` + +## License Arproxy is released under the MIT license: * www.opensource.org/licenses/MIT - -Copyright (c) 2023 Issei Naruta From ba10fe1679fb24cca89fef44a13ace0ba446cfa0 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 16 Sep 2024 17:09:29 +0900 Subject: [PATCH 52/68] `#execute` returns nil when proxy_chain returns nil --- README.md | 1 + lib/arproxy/connection_adapter_patch.rb | 18 ++++++++++++++---- spec/arproxy_spec.rb | 24 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1d9f4af..08bff77 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ class Readonly < Arproxy::Base super sql, name else Rails.logger.warn "#{name} (BLOCKED) #{sql}" + nil # return nil to block the query end end end diff --git a/lib/arproxy/connection_adapter_patch.rb b/lib/arproxy/connection_adapter_patch.rb index ca098b6..da192d4 100644 --- a/lib/arproxy/connection_adapter_patch.rb +++ b/lib/arproxy/connection_adapter_patch.rb @@ -68,8 +68,13 @@ def apply_patch(target_method) break if instance_methods.include?(:"#{target_method}_with_arproxy") define_method("#{target_method}_with_arproxy") do |sql, name=nil, **kwargs| ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send("#{target_method}_without_arproxy", _sql, _name, **kwargs) + proxy_chain_result = ::Arproxy.proxy_chain.head.execute(sql, name) + if proxy_chain_result && proxy_chain_result.is_a?(Array) + _sql, _name = proxy_chain_result + self.send("#{target_method}_without_arproxy", _sql, _name, **kwargs) + else + nil + end end alias_method :"#{target_method}_without_arproxy", target_method alias_method target_method, :"#{target_method}_with_arproxy" @@ -82,8 +87,13 @@ def apply_patch_binds(target_method) adapter_class.class_eval do define_method("#{target_method}_with_arproxy") do |sql, name=nil, binds=[], **kwargs| ::Arproxy.proxy_chain.connection = self - _sql, _name = *::Arproxy.proxy_chain.head.execute(sql, name) - self.send("#{target_method}_without_arproxy", _sql, _name, binds, **kwargs) + proxy_chain_result = ::Arproxy.proxy_chain.head.execute(sql, name) + if proxy_chain_result && proxy_chain_result.is_a?(Array) + _sql, _name = proxy_chain_result + self.send("#{target_method}_without_arproxy", _sql, _name, binds, **kwargs) + else + nil + end end alias_method :"#{target_method}_without_arproxy", target_method alias_method target_method, :"#{target_method}_with_arproxy" diff --git a/spec/arproxy_spec.rb b/spec/arproxy_spec.rb index ae9ce6a..0f1af0a 100644 --- a/spec/arproxy_spec.rb +++ b/spec/arproxy_spec.rb @@ -96,6 +96,30 @@ def execute2(sql, name = nil, binds = [], **kwargs) it { expect(connection.execute1('SQL', 'NAME')).to eq({ sql: 'SQL_A_B1', name: 'NAME_A_B1', kwargs: {} }) } end + context 'with a proxy that returns nil' do + class ReadonlyAccess < Arproxy::Base + def execute(sql, name) + if sql =~ /^(SELECT)\b/ + super sql, name + else + nil + end + end + end + + before do + Arproxy.clear_configuration + Arproxy.configure do |config| + config.adapter = 'dummy' + config.use ReadonlyAccess + end + Arproxy.enable! + end + + it { expect(connection.execute1('SELECT 1', 'NAME')).to eq({ sql: 'SELECT 1', name: 'NAME', kwargs: {} }) } + it { expect(connection.execute1('UPDATE foo SET bar = 1', 'NAME')).to eq(nil) } + end + context do before do Arproxy.clear_configuration From 9dc753c0ce8eaab492e0527c9e8e9628ed3125c4 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 16 Sep 2024 17:42:40 +0900 Subject: [PATCH 53/68] Add supported environments --- ChangeLog.md | 6 ++++++ README.md | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index e1a2605..fd3cb9d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,10 @@ # Change Log +## 1.0.0 +* Added support for ActiveRecord 7.1. +* Redesigned the proxy chain to accommodate internal structure changes in ActiveRecord 7.1. +* Introduced integration tests using real databases, allowing for more robust testing of functionality with MySQL, PostgreSQL, SQLite, and SQLServer. + See: https://github.com/cookpad/arproxy/issues/30 + ## 0.2.9 * Support ActiveRecord 7.0 (#21) Thanks to @r7kamura diff --git a/README.md b/README.md index 08bff77..cc5d5cc 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,26 @@ end +-------------------------+ +----------+ +----------+ +------------------+ ``` +## Supported Environments + +Arproxy supports the following databases and adapters: + +- MySQL + - `mysql2`, `trilogy` +- PostgreSQL + - `pg` +- SQLite + - `sqlite3` +- SQLServer + - `activerecord-sqlserver-adapter` + +We have tested with the following versions of Ruby and ActiveRecord: + +- Ruby + - `2.7`, `3.0`, `3.1`, `3.2`, `3.3` +- ActiveRecord + - `6.1`, `7.0`, `7.1`, `7.2` + ## Examples ### Slow Query Logger ```ruby From 210f8b935038c28193bd0ec6bd210d01bc9a0a30 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 13 Oct 2024 16:28:19 +0900 Subject: [PATCH 54/68] rubocop: Layout/EmptyLinesAroundAccessModifier: around --- .rubocop.yml | 3 +-- lib/arproxy.rb | 1 + lib/arproxy/config.rb | 1 + lib/arproxy/connection_adapter_patch.rb | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 61109a2..392b209 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -48,7 +48,7 @@ Layout/EmptyLineAfterMagicComment: Layout/EmptyLinesAroundAccessModifier: Enabled: true - EnforcedStyle: only_before + EnforcedStyle: around Layout/EmptyLinesAroundBlockBody: Enabled: true @@ -159,7 +159,6 @@ Layout/TrailingEmptyLines: # No trailing whitespace. Layout/TrailingWhitespace: Enabled: true - # Use quotes for string literals when they are enough. Style/RedundantPercentQ: Enabled: true diff --git a/lib/arproxy.rb b/lib/arproxy.rb index e757ac9..a430ad5 100644 --- a/lib/arproxy.rb +++ b/lib/arproxy.rb @@ -11,6 +11,7 @@ module Arproxy @patch = nil module_function + def clear_configuration @config = nil end diff --git a/lib/arproxy/config.rb b/lib/arproxy/config.rb index 28648e4..cc0cee2 100644 --- a/lib/arproxy/config.rb +++ b/lib/arproxy/config.rb @@ -36,6 +36,7 @@ def adapter_class end private + def camelized_adapter_name adapter_name = @adapter.to_s.split('_').map(&:capitalize).join diff --git a/lib/arproxy/connection_adapter_patch.rb b/lib/arproxy/connection_adapter_patch.rb index da192d4..66a0cb8 100644 --- a/lib/arproxy/connection_adapter_patch.rb +++ b/lib/arproxy/connection_adapter_patch.rb @@ -62,6 +62,7 @@ def disable! end private + def apply_patch(target_method) return if @applied_patches.include?(target_method) adapter_class.class_eval do From 5242ac175228848072d656e202a5f45ac7079510 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 13 Oct 2024 16:42:55 +0900 Subject: [PATCH 55/68] Un-commented a test case --- spec/integration/shared_examples/active_record_functions.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/integration/shared_examples/active_record_functions.rb b/spec/integration/shared_examples/active_record_functions.rb index 9970d64..e8b200a 100644 --- a/spec/integration/shared_examples/active_record_functions.rb +++ b/spec/integration/shared_examples/active_record_functions.rb @@ -16,8 +16,7 @@ end context 'SELECT' do - # it { expect(Product.where(name: ['apple', 'orange']).sum(:price)).to eq(400) } - it { expect(Product.count).to eq(3) } + it { expect(Product.where(name: ['apple', 'orange']).sum(:price)).to eq(400) } end context 'UPDATE' do From bb17093eb2331073b63afdfea1f09d3ccd2300ce Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 16:21:14 +0900 Subject: [PATCH 56/68] mv docker-compose.yml compose.yaml --- docker-compose.yml => compose.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docker-compose.yml => compose.yaml (100%) diff --git a/docker-compose.yml b/compose.yaml similarity index 100% rename from docker-compose.yml rename to compose.yaml From ac57386c88b8a9dc96f38869172b5cd071668fd1 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 16:28:51 +0900 Subject: [PATCH 57/68] Remove v1 branch from GH workflows --- .github/workflows/integration_tests.yml | 2 -- .github/workflows/rubocop.yml | 2 -- .github/workflows/unit_tests.yml | 2 -- 3 files changed, 6 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index ea7b660..a8b4a3a 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -4,11 +4,9 @@ on: push: branches: - main - - v1 pull_request: branches: - main - - v1 env: RUBY_VERSION: 3.3 diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index dcf84a7..ec86f0e 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -4,11 +4,9 @@ on: push: branches: - main - - v1 pull_request: branches: - main - - v1 jobs: rubocop: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 18df567..27e5959 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -4,11 +4,9 @@ on: push: branches: - main - - v1 pull_request: branches: - main - - v1 jobs: test: From cc8898a00848daaf26c29188a83d257069389665 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 16:29:13 +0900 Subject: [PATCH 58/68] Bump postgres version to 17 --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index 267e2f3..d56fe4b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,7 +13,7 @@ services: - "23306:3306" postgres: - image: postgres:16 + image: postgres:17 restart: always environment: POSTGRES_DB: ${ARPROXY_DB_DATABASE} From 06cdebec74c4fd428e448de130835415727612bd Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 16:30:05 +0900 Subject: [PATCH 59/68] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc5d5cc..7c0ba8a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ class QueryTracer < Arproxy::Base end Arproxy.configure do |config| - config.adapter = 'mysql2' # A DB Apdapter name which is used in your database.yml + config.adapter = 'mysql2' # A DB Adapter name which is used in your database.yml config.use QueryTracer end Arproxy.enable! From 5582caad2621181afa7c3e8273b9ac449dfaa750 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 16:40:37 +0900 Subject: [PATCH 60/68] Specify db versions in README --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c0ba8a..359ec38 100644 --- a/README.md +++ b/README.md @@ -74,12 +74,18 @@ Arproxy supports the following databases and adapters: - SQLServer - `activerecord-sqlserver-adapter` -We have tested with the following versions of Ruby and ActiveRecord: +We have tested with the following versions of Ruby, ActiveRecord, and databases: - Ruby - `2.7`, `3.0`, `3.1`, `3.2`, `3.3` - ActiveRecord - `6.1`, `7.0`, `7.1`, `7.2` +- MySQL + - `8.0` +- PostgreSQL + - `17` +- SQLServer + - `2022` ## Examples ### Slow Query Logger From fbf518bde021a60ae89a2378ba9050a8ad41d931 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 16:44:08 +0900 Subject: [PATCH 61/68] Use `!` methods to raise errors --- .../shared_examples/active_record_functions.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/integration/shared_examples/active_record_functions.rb b/spec/integration/shared_examples/active_record_functions.rb index e8b200a..c286e78 100644 --- a/spec/integration/shared_examples/active_record_functions.rb +++ b/spec/integration/shared_examples/active_record_functions.rb @@ -6,9 +6,9 @@ t.integer :price end # INSERT - Product.create(name: 'apple', price: 100) - Product.create(name: 'banana', price: 200) - Product.create(name: 'orange', price: 300) + Product.create!(name: 'apple', price: 100) + Product.create!(name: 'banana', price: 200) + Product.create!(name: 'orange', price: 300) end after(:all) do @@ -24,7 +24,7 @@ expect { Product.where(name: 'banana').update_all(price: 1000) }.to change { - Product.find_by(name: 'banana').price + Product.find_by!(name: 'banana').price }.from(200).to(1000) end end From e0172427791ca743075361c5b03c08cb529e80e4 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 19:02:24 +0900 Subject: [PATCH 62/68] Set timeout for integration_tests --- .github/workflows/integration_tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index a8b4a3a..904bbe9 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -14,6 +14,7 @@ env: jobs: integration_test: runs-on: ubuntu-latest + timeout-minutes: 5 env: BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile From 74de4a4552412caafd92411e5d5e3d7e4679900c Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 19:23:55 +0900 Subject: [PATCH 63/68] Remove redundant calling `Arproxy.clear_configuration` --- spec/arproxy_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/arproxy_spec.rb b/spec/arproxy_spec.rb index 0f1af0a..395a304 100644 --- a/spec/arproxy_spec.rb +++ b/spec/arproxy_spec.rb @@ -41,7 +41,6 @@ def execute2(sql, name = nil, binds = [], **kwargs) let(:connection) { ::ActiveRecord::ConnectionAdapters::DummyAdapter.new } after(:each) do Arproxy.disable! - Arproxy.clear_configuration end context 'with a proxy' do From aff80563f0abb425e13afe8a941e29b0cbd52f39 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 21:31:39 +0900 Subject: [PATCH 64/68] Bump mysql version from 8.0 to 9.0 --- .gitignore | 1 + README.md | 2 +- compose.yaml | 3 ++- db/mysql/my.cnf | 5 +++-- spec/integration/trilogy_spec.rb | 8 ++++++++ 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index dcb002a..ebc6712 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Gemfile.lock *.gemfile.lock .bundle/ tmp/ +db/mysql/data/* diff --git a/README.md b/README.md index 359ec38..749d9ac 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ We have tested with the following versions of Ruby, ActiveRecord, and databases: - ActiveRecord - `6.1`, `7.0`, `7.1`, `7.2` - MySQL - - `8.0` + - `9.0` - PostgreSQL - `17` - SQLServer diff --git a/compose.yaml b/compose.yaml index d56fe4b..100a67d 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,6 +1,6 @@ services: mysql: - image: mysql:8.0 + image: mysql:9.0 restart: always environment: MYSQL_ROOT_PASSWORD: rootpassword @@ -9,6 +9,7 @@ services: MYSQL_PASSWORD: ${ARPROXY_DB_PASSWORD} volumes: - ./db/mysql/my.cnf:/etc/mysql/conf.d/my.cnf + - ./db/mysql/data:/var/lib/mysql ports: - "23306:3306" diff --git a/db/mysql/my.cnf b/db/mysql/my.cnf index 943a446..1c15be9 100644 --- a/db/mysql/my.cnf +++ b/db/mysql/my.cnf @@ -1,3 +1,4 @@ [mysqld] -# Trilogy issue: https://github.com/trilogy-libraries/trilogy/issues/26 -default-authentication-plugin = mysql_native_password +require-secure-transport = ON +tls-version=TLSv1.2,TLSv1.3 +auto_generate_certs = ON diff --git a/spec/integration/trilogy_spec.rb b/spec/integration/trilogy_spec.rb index ee6fb8a..f3d9fd0 100644 --- a/spec/integration/trilogy_spec.rb +++ b/spec/integration/trilogy_spec.rb @@ -1,11 +1,19 @@ require_relative '../spec_helper' +require 'trilogy' context "Trilogy (AR#{ar_version})", if: ActiveRecord.version >= '7.1' do before(:all) do + mysql_data_dir = File.expand_path('../../db/mysql/data', __dir__) ActiveRecord::Base.establish_connection( adapter: 'trilogy', host: ENV.fetch('MYSQL_HOST', '127.0.0.1'), port: ENV.fetch('MYSQL_PORT', '23306').to_i, + ssl: true, + ssl_mode: Trilogy::SSL_VERIFY_CA, + tls_min_version: Trilogy::TLS_VERSION_12, + ssl_ca: File.join(mysql_data_dir, 'ca.pem'), + ssl_cert: File.join(mysql_data_dir, 'client-cert.pem'), + ssl_key: File.join(mysql_data_dir, 'client-key.pem'), database: 'arproxy_test', username: 'arproxy', password: ENV.fetch('ARPROXY_DB_PASSWORD') From 0532c9c8e669976df3ff18ae389a9738a4a4f23f Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 23:51:36 +0900 Subject: [PATCH 65/68] Add Dockerfile for CI --- .github/workflows/integration_tests.yml | 34 ++++--------- Dockerfile | 24 ++++++++++ compose-ci.yaml | 64 +++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 26 deletions(-) create mode 100644 Dockerfile create mode 100644 compose-ci.yaml diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 904bbe9..f932077 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -8,39 +8,21 @@ on: branches: - main -env: - RUBY_VERSION: 3.3 - jobs: integration_test: runs-on: ubuntu-latest timeout-minutes: 5 - env: - BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile - MYSQL_HOST: 127.0.0.1 - POSTGRES_HOST: 127.0.0.1 - MSSQL_HOST: 127.0.0.1 strategy: matrix: - gemfile: - - ar_6.1 - - ar_7.0 - - ar_7.1 - - ar_7.2 + appraisal: + - ar-6.1 + - ar-7.0 + - ar-7.1 + - ar-7.2 steps: - uses: actions/checkout@v4 - - name: Install build dependencies - run: | - sudo apt update - sudo apt install -y build-essential freetds-dev - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ env.RUBY_VERSION }} - - name: Start DB - run: docker compose up -d - - name: Install gems - run: bundle install + - name: docker compose up + run: docker compose -f compose-ci.yaml up -d - name: Run integration test - run: bundle exec rspec spec/integration/*_spec.rb + run: docker compose -f compose-ci.yaml exec ruby bundle exec appraisal ${{ matrix.appraisal }} rspec spec/integration/*_spec.rb diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..73a3f48 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM ruby:3.3 + +WORKDIR /app + +COPY lib lib +COPY spec spec +COPY gemfiles gemfiles + +COPY arproxy.gemspec arproxy.gemspec +COPY Gemfile Gemfile +COPY Appraisals Appraisals +COPY .env .env + +RUN apt update +RUN apt install -y build-essential freetds-dev + +RUN bundle install +RUN bundle exec appraisal install + +RUN mkdir -p /app/db/mysql +RUN ln -s /var/lib/mysql /app/db/mysql/data + +# dummy command to keep the container running +CMD ["sleep", "infinity"] diff --git a/compose-ci.yaml b/compose-ci.yaml new file mode 100644 index 0000000..1a6dba0 --- /dev/null +++ b/compose-ci.yaml @@ -0,0 +1,64 @@ +services: + ruby: + build: + context: . + dockerfile: Dockerfile + environment: + MYSQL_HOST: mysql + MYSQL_PORT: 3306 + POSTGRES_HOST: postgres + POSTGRES_PORT: 5432 + MSSQL_HOST: sqlserver + MSSQL_PORT: 1433 + volumes: + - mysql-data:/var/lib/mysql + + mysql: + image: mysql:9.0 + restart: always + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: ${ARPROXY_DB_DATABASE} + MYSQL_USER: ${ARPROXY_DB_USER} + MYSQL_PASSWORD: ${ARPROXY_DB_PASSWORD} + volumes: + - ./db/mysql/my.cnf:/etc/mysql/conf.d/my.cnf + - mysql-data:/var/lib/mysql + ports: + - "23306:3306" + + postgres: + image: postgres:17 + restart: always + environment: + POSTGRES_DB: ${ARPROXY_DB_DATABASE} + POSTGRES_USER: ${ARPROXY_DB_USER} + POSTGRES_PASSWORD: ${ARPROXY_DB_PASSWORD} + ports: + - "25432:5432" + + sqlserver: + image: mcr.microsoft.com/mssql/server:2022-latest + restart: always + environment: + ACCEPT_EULA: Y + MSSQL_SA_PASSWORD: R00tPassword12! + ports: + - "21433:1433" + healthcheck: + test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P R00tPassword12! -Q 'SELECT 1' || exit 1"] + interval: 5s + retries: 10 + start_period: 10s + + sqlserver-init: + image: mcr.microsoft.com/mssql/server:2022-latest + volumes: + - ./db/sqlserver/init.sql:/init.sql + command: /opt/mssql-tools18/bin/sqlcmd -C -S sqlserver -U sa -P R00tPassword12! -d master -i /init.sql + depends_on: + sqlserver: + condition: service_healthy + +volumes: + mysql-data: From 4c19c12857e8a8a3e0cbf1a2cb47a79533df8416 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Sun, 20 Oct 2024 23:54:49 +0900 Subject: [PATCH 66/68] Remove require-secure-transport for mysql2 --- db/mysql/my.cnf | 1 - 1 file changed, 1 deletion(-) diff --git a/db/mysql/my.cnf b/db/mysql/my.cnf index 1c15be9..6ed9fc5 100644 --- a/db/mysql/my.cnf +++ b/db/mysql/my.cnf @@ -1,4 +1,3 @@ [mysqld] -require-secure-transport = ON tls-version=TLSv1.2,TLSv1.3 auto_generate_certs = ON From 08941a3c65a9f336fc3be407e2d0ba56cfaf9cd2 Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 21 Oct 2024 00:00:21 +0900 Subject: [PATCH 67/68] Add sleep for debugging --- .github/workflows/integration_tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index f932077..2ca6003 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -24,5 +24,7 @@ jobs: - uses: actions/checkout@v4 - name: docker compose up run: docker compose -f compose-ci.yaml up -d + - name: wait for db # FIXME + run: sleep 10 - name: Run integration test run: docker compose -f compose-ci.yaml exec ruby bundle exec appraisal ${{ matrix.appraisal }} rspec spec/integration/*_spec.rb From add26f45b2993445e198c0cba6d69adba6734dbc Mon Sep 17 00:00:00 2001 From: Issei Naruta Date: Mon, 21 Oct 2024 00:25:45 +0900 Subject: [PATCH 68/68] Implement wait_for_db --- .github/workflows/integration_tests.yml | 2 -- spec/integration/mysql2_spec.rb | 8 ++++++-- spec/integration/postgresql_spec.rb | 8 ++++++-- spec/integration/sqlserver_spec.rb | 9 +++++++-- spec/integration/trilogy_spec.rb | 8 ++++++-- spec/spec_helper.rb | 16 ++++++++++++++++ 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 2ca6003..f932077 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -24,7 +24,5 @@ jobs: - uses: actions/checkout@v4 - name: docker compose up run: docker compose -f compose-ci.yaml up -d - - name: wait for db # FIXME - run: sleep 10 - name: Run integration test run: docker compose -f compose-ci.yaml exec ruby bundle exec appraisal ${{ matrix.appraisal }} rspec spec/integration/*_spec.rb diff --git a/spec/integration/mysql2_spec.rb b/spec/integration/mysql2_spec.rb index e54eb22..a518281 100644 --- a/spec/integration/mysql2_spec.rb +++ b/spec/integration/mysql2_spec.rb @@ -2,10 +2,14 @@ context "MySQL (AR#{ar_version})" do before(:all) do + host = ENV.fetch('MYSQL_HOST', '127.0.0.1') + port = ENV.fetch('MYSQL_PORT', '23306').to_i + wait_for_db(host, port) + ActiveRecord::Base.establish_connection( adapter: 'mysql2', - host: ENV.fetch('MYSQL_HOST', '127.0.0.1'), - port: ENV.fetch('MYSQL_PORT', '23306').to_i, + host: host, + port: port, database: 'arproxy_test', username: 'arproxy', password: ENV.fetch('ARPROXY_DB_PASSWORD') diff --git a/spec/integration/postgresql_spec.rb b/spec/integration/postgresql_spec.rb index 86f42ed..0d7a787 100644 --- a/spec/integration/postgresql_spec.rb +++ b/spec/integration/postgresql_spec.rb @@ -2,10 +2,14 @@ context "PostgreSQL (AR#{ar_version})" do before(:all) do + host = ENV.fetch('POSTGRES_HOST', '127.0.0.1') + port = ENV.fetch('POSTGRES_PORT', '25432').to_i + wait_for_db(host, port) + ActiveRecord::Base.establish_connection( adapter: 'postgresql', - host: ENV.fetch('POSTGRES_HOST', '127.0.0.1'), - port: ENV.fetch('POSTGRES_PORT', '25432').to_i, + host: host, + port: port, database: 'arproxy_test', username: 'arproxy', password: ENV.fetch('ARPROXY_DB_PASSWORD') diff --git a/spec/integration/sqlserver_spec.rb b/spec/integration/sqlserver_spec.rb index 57e0b6d..cac9fe6 100644 --- a/spec/integration/sqlserver_spec.rb +++ b/spec/integration/sqlserver_spec.rb @@ -9,10 +9,15 @@ 'active_record/connection_adapters/sqlserver_adapter' ) end + + host = ENV.fetch('MSSQL_HOST', '127.0.0.1') + port = ENV.fetch('MSSQL_PORT', '21433').to_i + wait_for_db(host, port) + ActiveRecord::Base.establish_connection( adapter: 'sqlserver', - host: ENV.fetch('MSSQL_HOST', '127.0.0.1'), - port: ENV.fetch('MSSQL_PORT', '21433').to_i, + host: host, + port: port, database: 'arproxy_test', username: 'arproxy', password: ENV.fetch('ARPROXY_DB_PASSWORD') diff --git a/spec/integration/trilogy_spec.rb b/spec/integration/trilogy_spec.rb index f3d9fd0..0f8da2b 100644 --- a/spec/integration/trilogy_spec.rb +++ b/spec/integration/trilogy_spec.rb @@ -3,11 +3,15 @@ context "Trilogy (AR#{ar_version})", if: ActiveRecord.version >= '7.1' do before(:all) do + host = ENV.fetch('MYSQL_HOST', '127.0.0.1') + port = ENV.fetch('MYSQL_PORT', '23306').to_i + wait_for_db(host, port) + mysql_data_dir = File.expand_path('../../db/mysql/data', __dir__) ActiveRecord::Base.establish_connection( adapter: 'trilogy', - host: ENV.fetch('MYSQL_HOST', '127.0.0.1'), - port: ENV.fetch('MYSQL_PORT', '23306').to_i, + host: host, + port: port, ssl: true, ssl_mode: Trilogy::SSL_VERIFY_CA, tls_min_version: Trilogy::TLS_VERSION_12, diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5e135e3..aa78f1c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -16,3 +16,19 @@ def cleanup_activerecord ActiveRecord::Base.descendants.each(&:reset_column_information) ActiveRecord::Base.connection.schema_cache.clear! end + +def wait_for_db(host, port, interval = 0.2, timeout = 10) + print "\nWaiting for DB on #{host}:#{port}..." if ENV['DEBUG'] + Timeout.timeout(timeout) do + loop do + TCPSocket.new(host, port).close + puts 'ok' if ENV['DEBUG'] + break + rescue Errno::ECONNREFUSED + print '.' if ENV['DEBUG'] + sleep interval + end + end +rescue Timeout::Error + raise "Timeout waiting for DB on #{host}:#{port}" +end