diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e05f4568..f794c252 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,9 @@ jobs: - 3.1 - 3.2 - 3.3 + adapter: + - trilogy + - mysql2 gemfile: - gemfiles/rails_6_1.gemfile - gemfiles/rails_7_0.gemfile @@ -19,6 +22,7 @@ jobs: env: PERCONA_DB_USER: root PERCONA_DB_PASSWORD: root + PERCONA_DB_ADAPTER: ${{ matrix.adapter }} BUNDLE_GEMFILE: ${{ matrix.gemfile }} runs-on: ubuntu-latest steps: diff --git a/.rubocop.yml b/.rubocop.yml index dc912a27..444c9ae0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,6 +14,7 @@ Metrics/AbcSize: Metrics/BlockLength: Max: 20 Exclude: + - 'departure.gemspec' - 'spec/*' - 'spec/**/*' diff --git a/Appraisals b/Appraisals index adc0c28e..d18776e2 100644 --- a/Appraisals +++ b/Appraisals @@ -1,8 +1,10 @@ appraise 'rails-6-1' do + gem 'activerecord-trilogy-adapter', '>= 3.1.2', require: false gem 'rails', '6.1.7.6' end appraise 'rails-7-0' do + gem 'activerecord-trilogy-adapter', '>= 3.1.2', require: false gem 'rails', '7.0.8' end diff --git a/Gemfile.lock b/Gemfile.lock index a10211f8..1bb56fd1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,6 @@ PATH specs: departure (6.7.0) activerecord (>= 6.0.0, < 7.2.0, != 7.0.0) - mysql2 (>= 0.4.0, < 0.6.0) railties (>= 6.0.0, < 7.2.0, != 7.0.0) GEM @@ -168,6 +167,7 @@ GEM stringio (3.1.0) thor (1.3.0) timeout (0.4.1) + trilogy (2.7.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) @@ -185,12 +185,14 @@ DEPENDENCIES climate_control (~> 0.0.3) codeclimate-test-reporter (~> 1.0.3) departure! + mysql2 (>= 0.4.0, <= 0.5.5) pry-byebug rake (>= 10.0) rspec (~> 3.4, >= 3.4.0) rspec-its (~> 1.2) rubocop (~> 1.60.2) rubocop-performance (~> 1.20.2) + trilogy (>= 2.7.0) BUNDLED WITH 2.4.22 diff --git a/config.yml.erb b/config.yml.erb index e46248cc..d24d2095 100644 --- a/config.yml.erb +++ b/config.yml.erb @@ -3,3 +3,4 @@ password: <%= ENV['PERCONA_DB_PASSWORD'] || '' %> database: <%= ENV['PERCONA_DB_NAME'] || 'departure_test' %> hostname: <%= ENV['PERCONA_DB_HOST'] || 'localhost' %> socket: <%= ENV['PERCONA_DB_SOCKET'] || '' %> +original_adapter: <%= ENV['PERCONA_DB_ADAPTER'] || 'mysql2' %> diff --git a/departure.gemspec b/departure.gemspec index 68bea56a..1bb91666 100644 --- a/departure.gemspec +++ b/departure.gemspec @@ -23,12 +23,13 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'railties', '>= 6.0.0', '!= 7.0.0', '< 7.2.0' spec.add_runtime_dependency 'activerecord', '>= 6.0.0', '!= 7.0.0', '< 7.2.0' - spec.add_runtime_dependency 'mysql2', '>= 0.4.0', '< 0.6.0' spec.add_development_dependency 'appraisal', '~> 2.4.1' + spec.add_development_dependency 'climate_control', '~> 0.0.3' + spec.add_development_dependency 'mysql2', '>= 0.4.0', '<= 0.5.5' + spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'rake', '>= 10.0' - spec.add_development_dependency 'rspec', '~> 3.4', '>= 3.4.0' spec.add_development_dependency 'rspec-its', '~> 1.2' - spec.add_development_dependency 'pry-byebug' - spec.add_development_dependency 'climate_control', '~> 0.0.3' + spec.add_development_dependency 'rspec', '~> 3.4', '>= 3.4.0' + spec.add_development_dependency 'trilogy', '>= 2.7.0' end diff --git a/gemfiles/rails_6_1.gemfile b/gemfiles/rails_6_1.gemfile index 65745ff4..a3bf085d 100644 --- a/gemfiles/rails_6_1.gemfile +++ b/gemfiles/rails_6_1.gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' +gem 'activerecord-trilogy-adapter', '>= 3.1.2', require: false gem 'codeclimate-test-reporter', '~> 1.0.3', group: :test, require: nil gem 'rails', '6.1.7.6' gem 'rubocop', '~> 1.60.2', require: false diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index 4c893627..e4bfa491 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -3,7 +3,6 @@ PATH specs: departure (6.7.0) activerecord (>= 6.0.0, < 7.2.0, != 7.0.0) - mysql2 (>= 0.4.0, < 0.6.0) railties (>= 6.0.0, < 7.2.0, != 7.0.0) GEM @@ -55,6 +54,9 @@ GEM activerecord (6.1.7.6) activemodel (= 6.1.7.6) activesupport (= 6.1.7.6) + activerecord-trilogy-adapter (3.1.2) + activerecord (>= 6.0.a, < 7.1.a) + trilogy (>= 2.4.0) activestorage (6.1.7.6) actionpack (= 6.1.7.6) activejob (= 6.1.7.6) @@ -214,6 +216,7 @@ GEM strscan (3.1.0) thor (1.3.1) timeout (0.4.1) + trilogy (2.7.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) @@ -227,10 +230,12 @@ PLATFORMS x86_64-linux DEPENDENCIES + activerecord-trilogy-adapter (>= 3.1.2) appraisal (~> 2.4.1) climate_control (~> 0.0.3) codeclimate-test-reporter (~> 1.0.3) departure! + mysql2 (>= 0.4.0, <= 0.5.5) pry-byebug rails (= 6.1.7.6) rake (>= 10.0) @@ -238,6 +243,7 @@ DEPENDENCIES rspec-its (~> 1.2) rubocop (~> 1.60.2) rubocop-performance (~> 1.20.2) + trilogy (>= 2.7.0) BUNDLED WITH 2.4.22 diff --git a/gemfiles/rails_7_0.gemfile b/gemfiles/rails_7_0.gemfile index 09345c27..06e086f7 100644 --- a/gemfiles/rails_7_0.gemfile +++ b/gemfiles/rails_7_0.gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' +gem 'activerecord-trilogy-adapter', '>= 3.1.2', require: false gem 'codeclimate-test-reporter', '~> 1.0.3', group: :test, require: nil gem 'rails', '7.0.8' gem 'rubocop', '~> 1.60.2', require: false diff --git a/gemfiles/rails_7_0.gemfile.lock b/gemfiles/rails_7_0.gemfile.lock index 3dcad3a9..a3f7bf92 100644 --- a/gemfiles/rails_7_0.gemfile.lock +++ b/gemfiles/rails_7_0.gemfile.lock @@ -3,7 +3,6 @@ PATH specs: departure (6.7.0) activerecord (>= 6.0.0, < 7.2.0, != 7.0.0) - mysql2 (>= 0.4.0, < 0.6.0) railties (>= 6.0.0, < 7.2.0, != 7.0.0) GEM @@ -62,6 +61,9 @@ GEM activerecord (7.0.8) activemodel (= 7.0.8) activesupport (= 7.0.8) + activerecord-trilogy-adapter (3.1.2) + activerecord (>= 6.0.a, < 7.1.a) + trilogy (>= 2.4.0) activestorage (7.0.8) actionpack (= 7.0.8) activejob (= 7.0.8) @@ -213,6 +215,7 @@ GEM strscan (3.1.0) thor (1.3.1) timeout (0.4.1) + trilogy (2.7.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) @@ -226,10 +229,12 @@ PLATFORMS x86_64-linux DEPENDENCIES + activerecord-trilogy-adapter (>= 3.1.2) appraisal (~> 2.4.1) climate_control (~> 0.0.3) codeclimate-test-reporter (~> 1.0.3) departure! + mysql2 (>= 0.4.0, <= 0.5.5) pry-byebug rails (= 7.0.8) rake (>= 10.0) @@ -237,6 +242,7 @@ DEPENDENCIES rspec-its (~> 1.2) rubocop (~> 1.60.2) rubocop-performance (~> 1.20.2) + trilogy (>= 2.7.0) BUNDLED WITH 2.4.22 diff --git a/gemfiles/rails_7_1.gemfile.lock b/gemfiles/rails_7_1.gemfile.lock index 87b8e786..8e35a3b0 100644 --- a/gemfiles/rails_7_1.gemfile.lock +++ b/gemfiles/rails_7_1.gemfile.lock @@ -3,7 +3,6 @@ PATH specs: departure (6.7.0) activerecord (>= 6.0.0, < 7.2.0, != 7.0.0) - mysql2 (>= 0.4.0, < 0.6.0) railties (>= 6.0.0, < 7.2.0, != 7.0.0) GEM @@ -244,6 +243,7 @@ GEM strscan (3.1.0) thor (1.3.1) timeout (0.4.1) + trilogy (2.7.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) @@ -262,6 +262,7 @@ DEPENDENCIES climate_control (~> 0.0.3) codeclimate-test-reporter (~> 1.0.3) departure! + mysql2 (>= 0.4.0, <= 0.5.5) pry-byebug rails (= 7.1.3) rake (>= 10.0) @@ -269,6 +270,7 @@ DEPENDENCIES rspec-its (~> 1.2) rubocop (~> 1.60.2) rubocop-performance (~> 1.20.2) + trilogy (>= 2.7.0) BUNDLED WITH 2.4.22 diff --git a/lib/active_record/connection_adapters/percona_adapter.rb b/lib/active_record/connection_adapters/percona_adapter.rb index 1eab221e..5f31bac1 100644 --- a/lib/active_record/connection_adapters/percona_adapter.rb +++ b/lib/active_record/connection_adapters/percona_adapter.rb @@ -1,6 +1,5 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' require 'active_record/connection_adapters/statement_pool' -require 'active_record/connection_adapters/mysql2_adapter' require 'active_support/core_ext/string/filters' require 'departure' require 'forwardable' @@ -14,7 +13,8 @@ def percona_connection(config) config = config.dup if config.frozen? config[:username] = 'root' end - mysql2_connection = mysql2_connection(config) + adapter = config[:original_adapter] + connection = send(Departure.connection_method(adapter), config) connection_details = Departure::ConnectionDetails.new(config) verbose = ActiveRecord::Migration.verbose @@ -23,19 +23,12 @@ def percona_connection(config) ] percona_logger = Departure::LoggerFactory.build(sanitizers: sanitizers, verbose: verbose) cli_generator = Departure::CliGenerator.new(connection_details) - - runner = Departure::Runner.new( - percona_logger, - cli_generator, - mysql2_connection - ) - - connection_options = { mysql_adapter: mysql2_connection } + runner = Departure::Runner.new(percona_logger, cli_generator, connection) ConnectionAdapters::DepartureAdapter.new( runner, logger, - connection_options, + { mysql_adapter: connection }, config ) end diff --git a/lib/departure.rb b/lib/departure.rb index be85c674..019af156 100644 --- a/lib/departure.rb +++ b/lib/departure.rb @@ -28,8 +28,22 @@ end module Departure + SUPPORTED_ADAPTERS = %w[trilogy mysql2].freeze + class << self attr_accessor :configuration + + def connection_method(adapter) + return "#{adapter}_connection" if Departure::SUPPORTED_ADAPTERS.include?(adapter) + + if adapter.blank? + raise ArgumentError, 'You must supply the original_adapter when connecting ' \ + "using the percona adapter. Supported adapters: #{SUPPORTED_ADAPTERS}" + end + + raise ArgumentError, "Unsupported adater #{adapter}. Supported Departure " \ + "adapters are #{Departure::SUPPORTED_ADAPTERS.inspect}" + end end def self.configure diff --git a/lib/departure/migration.rb b/lib/departure/migration.rb index 274ff1f0..c8ca62e6 100644 --- a/lib/departure/migration.rb +++ b/lib/departure/migration.rb @@ -63,6 +63,9 @@ def migrate(direction) # Includes the Foreigner's Mysql2Adapter implemention in # DepartureAdapter to support foreign keys + # + # Warning: Foreigner only works with MySQL2, and therefore + # trilogy is not supported def include_foreigner Foreigner::Adapter.safe_include( :DepartureAdapter, @@ -74,7 +77,8 @@ def include_foreigner # instead of the current adapter. def reconnect_with_percona return if connection_config[:adapter] == 'percona' - Departure::ConnectionBase.establish_connection(connection_config.merge(adapter: 'percona')) + Departure::ConnectionBase.establish_connection(connection_config.merge(adapter: 'percona', + original_adapter: original_adapter)) end # Reconnect without percona adapter when Departure is disabled but was diff --git a/spec/active_record/connection_adapters/percona_adapter/column_spec.rb b/spec/active_record/connection_adapters/percona_adapter/column_spec.rb new file mode 100644 index 00000000..8675852f --- /dev/null +++ b/spec/active_record/connection_adapters/percona_adapter/column_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe ActiveRecord::ConnectionAdapters::DepartureAdapter::Column do + let(:field) { double(:field) } + let(:default) { double(:default) } + let(:cast_type) do + if defined?(ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString) + ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString.new + else + ActiveRecord::Type.lookup(:string, adapter: :mysql2) + end + end + let(:metadata) do + ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new( + type: cast_type.type, + sql_type: type, + limit: cast_type.limit + ) + end + let(:mysql_metadata) do + ActiveRecord::ConnectionAdapters::MySQL::TypeMetadata.new(metadata) + end + let(:type) { 'VARCHAR' } + let(:null) { double(:null) } + let(:collation) { double(:collation) } + + let(:column) do + if ActiveRecord::VERSION::STRING >= '6.1' + described_class.new('field', 'default', mysql_metadata, null, collation: 'collation') + else + described_class.new(field, default, mysql_metadata, null, collation: collation) + end + end + + describe '#adapter' do + subject { column.adapter } + it do + is_expected.to eq( + ActiveRecord::ConnectionAdapters::DepartureAdapter + ) + end + end +end diff --git a/spec/active_record/connection_adapters/percona_adapter_spec.rb b/spec/active_record/connection_adapters/percona_adapter_spec.rb index c0ee6b8c..c9033462 100644 --- a/spec/active_record/connection_adapters/percona_adapter_spec.rb +++ b/spec/active_record/connection_adapters/percona_adapter_spec.rb @@ -1,322 +1,288 @@ require 'spec_helper' +require 'active_record/connection_adapters/mysql2_adapter' +require 'active_record/connection_adapters/trilogy_adapter' + describe ActiveRecord::ConnectionAdapters::DepartureAdapter do - describe ActiveRecord::ConnectionAdapters::DepartureAdapter::Column do - let(:field) { double(:field) } - let(:default) { double(:default) } - let(:cast_type) do - if defined?(ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString) - ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString.new - else - ActiveRecord::Type.lookup(:string, adapter: :mysql2) - end + shared_examples_for ActiveRecord::ConnectionAdapters::DepartureAdapter do |adapter_class, result_class| + let(:mysql_client) { double(:mysql_client, server_info: { version: '5.7.19' }) } + let(:mysql_adapter) do + instance_double(adapter_class, raw_connection: mysql_client) end - let(:metadata) do - ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new( - type: cast_type.type, - sql_type: type, - limit: cast_type.limit + let(:logger) { double(:logger, puts: true) } + let(:connection_options) { { mysql_adapter: mysql_adapter } } + + let(:runner) { instance_double(Departure::Runner) } + let(:cli_generator) do + instance_double( + Departure::CliGenerator, + generate: 'percona command' ) end - let(:mysql_metadata) do - ActiveRecord::ConnectionAdapters::MySQL::TypeMetadata.new(metadata) - end - let(:type) { 'VARCHAR' } - let(:null) { double(:null) } - let(:collation) { double(:collation) } - - let(:column) do - if ActiveRecord::VERSION::STRING >= "6.1" - described_class.new("field", "default", mysql_metadata, null, collation: "collation") - elsif ActiveRecord::VERSION::MAJOR == 6 - described_class.new(field, default, mysql_metadata, null, collation: collation) - else - described_class.new(field, default, mysql_metadata, type, null, collation) - end - end - - describe '#adapter' do - subject { column.adapter } - it do - is_expected.to eq( - ActiveRecord::ConnectionAdapters::DepartureAdapter - ) - end - end - end - - let(:mysql_adapter) do - instance_double(ActiveRecord::ConnectionAdapters::Mysql2Adapter) - end - let(:logger) { double(:logger, puts: true) } - let(:connection_options) { { mysql_adapter: mysql_adapter } } + let(:config) { { prepared_statements: '', runner: runner } } - let(:runner) { instance_double(Departure::Runner) } - let(:cli_generator) do - instance_double( - Departure::CliGenerator, - generate: 'percona command' - ) - end - - let(:config) { { prepared_statements: '', runner: runner } } + subject(:adapter) do + described_class.new(runner, logger, connection_options, config) + end - let(:adapter) do - described_class.new(runner, logger, connection_options, config) - end + before do + allow(runner).to( + receive(:execute).with('percona command').and_return(true) + ) + allow(Departure::CliGenerator).to( + receive(:new).and_return(cli_generator) + ) + allow(Departure::Runner).to( + receive(:new).with(logger) + ).and_return(runner) + end - let(:mysql_client) { double(:mysql_client) } - - before do - allow(mysql_client).to receive(:server_info).and_return(version: '5.7.19') - allow(mysql_adapter).to receive(:raw_connection).and_return(mysql_client) - allow(runner).to( - receive(:execute).with('percona command').and_return(true) - ) - allow(Departure::CliGenerator).to( - receive(:new).and_return(cli_generator) - ) - allow(Departure::Runner).to( - receive(:new).with(logger) - ).and_return(runner) - end + it { is_expected.to be_supports_migrations } - describe '#supports_migrations?' do - subject { adapter.supports_migrations? } - it { is_expected.to be true } - end + describe '#new_column' do + let(:field) { double(:field) } + let(:default) { double(:default) } + let(:type) { double(:type) } + let(:null) { double(:null) } + let(:collation) { double(:collation) } + let(:table_name) { double(:table_name) } + let(:default_function) { double(:default_function) } + let(:comment) { double(:comment) } - describe '#new_column' do - let(:field) { double(:field) } - let(:default) { double(:default) } - let(:type) { double(:type) } - let(:null) { double(:null) } - let(:collation) { double(:collation) } - let(:table_name) { double(:table_name) } - let(:default_function) { double(:default_function) } - let(:comment) { double(:comment) } - - it do - expect(ActiveRecord::ConnectionAdapters::DepartureAdapter::Column).to receive(:new) - adapter.new_column(field, default, type, null, table_name, default_function, collation, comment) + it do + expect(ActiveRecord::ConnectionAdapters::DepartureAdapter::Column).to receive(:new) + subject.new_column(field, default, type, null, table_name, default_function, collation, comment) + end end - end - describe 'schema statements' do - describe '#add_index' do - let(:table_name) { :foo } - let(:column_name) { :bar_id } - let(:index_name) { 'index_name' } - let(:options) { {type: 'index_type'} } - let(:index_type) { options[:type].upcase } - let(:sql) { 'ADD index_type INDEX `index_name` (bar_id)' } - let(:index_options) do - if ActiveRecord::VERSION::STRING >= '6.1' - [ - ActiveRecord::ConnectionAdapters::IndexDefinition.new( - table_name, - index_name, + describe 'schema statements' do + describe '#add_index' do + let(:table_name) { :foo } + let(:column_name) { :bar_id } + let(:index_name) { 'index_name' } + let(:options) { {type: 'index_type'} } + let(:index_type) { options[:type].upcase } + let(:sql) { 'ADD index_type INDEX `index_name` (bar_id)' } + let(:index_options) do + if ActiveRecord::VERSION::STRING >= '6.1' + [ + ActiveRecord::ConnectionAdapters::IndexDefinition.new( + table_name, + index_name, + nil, + [column_name], + **options + ), nil, - [column_name], - **options - ), - nil, - false - ] - else - [index_name, index_type, "#{column_name}"] + false + ] + else + [index_name, index_type, "#{column_name}"] + end end - end - let(:expected_sql) do - if ActiveRecord::VERSION::STRING >= '6.1' - "ALTER TABLE `#{table_name}` ADD #{index_type} INDEX `#{index_name}` (`#{column_name}`)" - else - "ALTER TABLE `#{table_name}` ADD #{index_type} INDEX `#{index_name}` (#{column_name})" + let(:expected_sql) do + if ActiveRecord::VERSION::STRING >= '6.1' + "ALTER TABLE `#{table_name}` ADD #{index_type} INDEX `#{index_name}` (`#{column_name}`)" + else + "ALTER TABLE `#{table_name}` ADD #{index_type} INDEX `#{index_name}` (#{column_name})" + end end - end - before do - allow(adapter).to( - receive(:add_index_options) - .with(table_name, column_name, options) - .and_return(index_options) - ) + before do + allow(adapter).to( + receive(:add_index_options) + .with(table_name, column_name, options) + .and_return(index_options) + ) + end + + it 'passes the built SQL to #execute' do + expect(adapter).to receive(:execute).with(expected_sql) + adapter.add_index(table_name, column_name, options) + end end - it 'passes the built SQL to #execute' do - expect(adapter).to receive(:execute).with(expected_sql) - adapter.add_index(table_name, column_name, options) + describe '#remove_index' do + let(:table_name) { :foo } + let(:options) { { column: :bar_id } } + let(:sql) { 'DROP INDEX `index_name`' } + + before do + allow(adapter).to( + receive(:index_name_for_remove) + .with(table_name, options) + .and_return('index_name') + ) + allow(adapter).to( + receive(:index_name_for_remove) + .with(table_name, nil, options) + .and_return('index_name') + ) + end + + it 'passes the built SQL to #execute' do + expect(adapter).to( + receive(:execute) + .with("ALTER TABLE `#{table_name}` DROP INDEX `index_name`") + ) + adapter.remove_index(table_name, **options) + end end end - describe '#remove_index' do - let(:table_name) { :foo } - let(:options) { { column: :bar_id } } - let(:sql) { 'DROP INDEX `index_name`' } + describe '#exec_delete' do + let(:sql) { 'DELETE FROM comments WHERE id = 1' } + let(:affected_rows) { 1 } + let(:name) { nil } + let(:binds) { nil } before do - allow(adapter).to( - receive(:index_name_for_remove) - .with(table_name, options) - .and_return('index_name') - ) - allow(adapter).to( - receive(:index_name_for_remove) - .with(table_name, nil, options) - .and_return('index_name') - ) + allow(runner).to receive(:query).with(anything) + allow(mysql_client).to receive(:affected_rows).and_return(affected_rows) end - it 'passes the built SQL to #execute' do - expect(adapter).to( - receive(:execute) - .with("ALTER TABLE `#{table_name}` DROP INDEX `index_name`") - ) - adapter.remove_index(table_name, **options) + it 'executes the sql' do + expect(adapter).to(receive(:execute).with(sql, name)) + adapter.exec_delete(sql, name, binds) end - end - end - - describe '#exec_delete' do - let(:sql) { 'DELETE FROM comments WHERE id = 1' } - let(:affected_rows) { 1 } - let(:name) { nil } - let(:binds) { nil } - before do - allow(runner).to receive(:query).with(anything) - allow(mysql_client).to receive(:affected_rows).and_return(affected_rows) + it 'returns the number of affected rows' do + expect(adapter.exec_delete(sql, name, binds)).to eq(affected_rows) + end end - it 'executes the sql' do - expect(adapter).to(receive(:execute).with(sql, name)) - adapter.exec_delete(sql, name, binds) - end + describe '#exec_insert' do + let(:sql) { 'INSERT INTO comments (id) VALUES (20)' } + let(:name) { nil } + let(:binds) { nil } - it 'returns the number of affected rows' do - expect(adapter.exec_delete(sql, name, binds)).to eq(affected_rows) + it 'executes the sql' do + expect(subject).to(receive(:execute).with(sql, name)) + subject.exec_insert(sql, name, binds) + end end - end - describe '#exec_insert' do - let(:sql) { 'INSERT INTO comments (id) VALUES (20)' } - let(:name) { nil } - let(:binds) { nil } + describe '#exec_query' do + let(:sql) { 'SELECT * FROM comments' } + let(:name) { nil } + let(:binds) { nil } - it 'executes the sql' do - expect(adapter).to(receive(:execute).with(sql, name)) - adapter.exec_insert(sql, name, binds) - end - end - - describe '#exec_query' do - let(:sql) { 'SELECT * FROM comments' } - let(:name) { nil } - let(:binds) { nil } + before do + allow(runner).to receive(:query).with(sql) + allow(subject).to( + receive(:execute).with(sql, name).and_return(result_set) + ) + end - before do - allow(runner).to receive(:query).with(sql) - allow(adapter).to( - receive(:execute).with(sql, name).and_return(result_set) - ) - end + context 'when the adapter returns results' do + let(:result_set) { double(fields: [:id], to_a: [1]) } - context 'when the adapter returns results' do - let(:result_set) { double(fields: [:id], to_a: [1]) } + it 'executes the sql' do + expect(adapter).to( + receive(:execute).with(sql, name) + ).and_return(result_set) - it 'executes the sql' do - expect(adapter).to( - receive(:execute).with(sql, name) - ).and_return(result_set) + adapter.exec_query(sql, name, binds) + end - adapter.exec_query(sql, name, binds) + it 'returns an ActiveRecord::Result' do + expect(ActiveRecord::Result).to( + receive(:new).with(result_set.fields, result_set.to_a) + ) + adapter.exec_query(sql, name, binds) + end end - it 'returns an ActiveRecord::Result' do - expect(ActiveRecord::Result).to( - receive(:new).with(result_set.fields, result_set.to_a) - ) - adapter.exec_query(sql, name, binds) - end - end + context 'when the adapter returns nil' do + let(:result_set) { nil } - context 'when the adapter returns nil' do - let(:result_set) { nil } + it 'executes the sql' do + expect(adapter).to( + receive(:execute).with(sql, name) + ).and_return(result_set) - it 'executes the sql' do - expect(adapter).to( - receive(:execute).with(sql, name) - ).and_return(result_set) + adapter.exec_query(sql, name, binds) + end - adapter.exec_query(sql, name, binds) + it 'returns an ActiveRecord::Result' do + expect(ActiveRecord::Result).to( + receive(:new).with(nil, []) + ) + adapter.exec_query(sql, name, binds) + end end + end - it 'returns an ActiveRecord::Result' do - expect(ActiveRecord::Result).to( - receive(:new).with(nil, []) + describe '#last_inserted_id' do + let(:result) { double(:result) } + + it 'delegates to the mysql adapter' do + expect(mysql_adapter).to( + receive(:last_inserted_id).with(result) ) - adapter.exec_query(sql, name, binds) + adapter.last_inserted_id(result) end end - end - describe '#last_inserted_id' do - let(:result) { double(:result) } + describe '#select_rows' do + subject { adapter.select_rows(sql, name) } - it 'delegates to the mysql adapter' do - expect(mysql_adapter).to( - receive(:last_inserted_id).with(result) - ) - adapter.last_inserted_id(result) - end - end + let(:sql) { 'SELECT id, body FROM comments' } + let(:name) { nil } - describe '#select_rows' do - subject { adapter.select_rows(sql, name) } + let(:array_of_rows) { [%w[1 body], %w[2 body]] } + let(:mysql_result) do + instance_double(result_class, to_a: array_of_rows, fields: [:id, :body]) + end - let(:sql) { 'SELECT id, body FROM comments' } - let(:name) { nil } + before do + allow(adapter).to( + receive(:execute).with(sql, name) + ).and_return(mysql_result) + end - let(:array_of_rows) { [%w[1 body], %w[2 body]] } - let(:mysql2_result) do - instance_double(Mysql2::Result, to_a: array_of_rows, fields: [:id, :body]) + it { is_expected.to match_array(array_of_rows) } end - before do - allow(adapter).to( - receive(:execute).with(sql, name) - ).and_return(mysql2_result) - end + describe '#select' do + subject { adapter.select(sql, name) } - it { is_expected.to match_array(array_of_rows) } - end + let(:sql) { 'SELECT id, body FROM comments' } + let(:name) { nil } - describe '#select' do - subject { adapter.select(sql, name) } + let(:array_of_rows) { [%w[1 body], %w[2 body]] } + let(:mysql_result) do + instance_double(result_class, fields: %w[id body], to_a: array_of_rows) + end - let(:sql) { 'SELECT id, body FROM comments' } - let(:name) { nil } + before do + allow(adapter).to( + receive(:execute).with(sql, name) + ).and_return(mysql_result) + end - let(:array_of_rows) { [%w[1 body], %w[2 body]] } - let(:mysql2_result) do - instance_double(Mysql2::Result, fields: %w[id body], to_a: array_of_rows) + it do + is_expected.to match_array( + [ + { 'id' => '1', 'body' => 'body' }, + { 'id' => '2', 'body' => 'body' } + ] + ) + end end + end - before do - allow(adapter).to( - receive(:execute).with(sql, name) - ).and_return(mysql2_result) - end + context "with a Mysql2Adapter backend" do + it_behaves_like ActiveRecord::ConnectionAdapters::DepartureAdapter, + ActiveRecord::ConnectionAdapters::Mysql2Adapter, + Mysql2::Result + end - it do - is_expected.to match_array( - [ - { 'id' => '1', 'body' => 'body' }, - { 'id' => '2', 'body' => 'body' } - ] - ) - end + context "with a TrilogyAdapter backend" do + it_behaves_like ActiveRecord::ConnectionAdapters::DepartureAdapter, + ActiveRecord::ConnectionAdapters::TrilogyAdapter, + Trilogy::Result end end diff --git a/spec/departure/runner_spec.rb b/spec/departure/runner_spec.rb index bb8ece00..01754731 100644 --- a/spec/departure/runner_spec.rb +++ b/spec/departure/runner_spec.rb @@ -1,56 +1,69 @@ require 'spec_helper' require 'tempfile' +require 'active_record/connection_adapters/mysql2_adapter' +require 'active_record/connection_adapters/trilogy_adapter' + describe Departure::Runner do - let(:command_line) { 'pt-online-schema-change command' } - let(:logger) { instance_double(Departure::Logger) } - let(:cli_generator) { instance_double(Departure::CliGenerator) } - let(:mysql_adapter) do - instance_double(ActiveRecord::ConnectionAdapters::Mysql2Adapter) - end - let(:config) do - instance_double( - Departure::Configuration, - error_log_path: 'departure_error.log', - redirect_stderr: true, - ) - end + shared_examples_for Departure::Runner do |adapter| + let(:command_line) { 'pt-online-schema-change command' } + let(:logger) { instance_double(Departure::Logger) } + let(:cli_generator) { instance_double(Departure::CliGenerator) } + let(:mysql_adapter) do + instance_double(adapter) + end + let(:config) do + instance_double( + Departure::Configuration, + error_log_path: 'departure_error.log', + redirect_stderr: true, + ) + end - let(:runner) { described_class.new(logger, cli_generator, mysql_adapter, config) } + let(:runner) { described_class.new(logger, cli_generator, mysql_adapter, config) } - describe '#query' do - end + describe '#query' do + end - describe '#affected_rows' do - let(:mysql_client) { double(:mysql_client) } + describe '#affected_rows' do + let(:mysql_client) { double(:mysql_client) } - before do - allow(mysql_adapter).to receive(:raw_connection).and_return(mysql_client) - end + before do + allow(mysql_adapter).to receive(:raw_connection).and_return(mysql_client) + end - it 'delegates to the MySQL adapter\'s client' do - expect(mysql_client).to receive(:affected_rows) - runner.affected_rows + it 'delegates to the MySQL adapter\'s client' do + expect(mysql_client).to receive(:affected_rows) + runner.affected_rows + end end - end - describe '#execute' do - let(:status) { instance_double(Process::Status) } - let(:cmd) { instance_double(Departure::Command, run: status) } + describe '#execute' do + let(:status) { instance_double(Process::Status) } + let(:cmd) { instance_double(Departure::Command, run: status) } - before do - allow(Departure::Command) - .to receive(:new).with(command_line, config.error_log_path, logger, config.redirect_stderr) - .and_return(cmd) - end + before do + allow(Departure::Command) + .to receive(:new).with(command_line, config.error_log_path, logger, config.redirect_stderr) + .and_return(cmd) + end - it 'executes the pt-online-schema-change command' do - runner.execute(command_line) - expect(cmd).to have_received(:run) - end + it 'executes the pt-online-schema-change command' do + runner.execute(command_line) + expect(cmd).to have_received(:run) + end - it 'returns the command status' do - expect(runner.execute(command_line)).to eq(status) + it 'returns the command status' do + expect(runner.execute(command_line)).to eq(status) + end end end + + context 'with a Mysql2Adapter backend' do + it_behaves_like Departure::Runner, ActiveRecord::ConnectionAdapters::Mysql2Adapter + end + + context 'with a TrilogyAdapter backend' do + it_behaves_like Departure::Runner, ActiveRecord::ConnectionAdapters::TrilogyAdapter + end end diff --git a/spec/integration_spec.rb b/spec/integration_spec.rb index c7792986..1a511022 100644 --- a/spec/integration_spec.rb +++ b/spec/integration_spec.rb @@ -67,10 +67,12 @@ class Comment < ActiveRecord::Base; end before do ActiveRecord::Base.establish_connection( adapter: 'percona', + original_adapter: db_config['original_adapter'], host: db_config['hostname'], username: db_config['username'], password: db_config['password'], - database: db_config['database'] + database: db_config['database'], + ssl_mode: 'required' ) end @@ -84,9 +86,11 @@ class Comment < ActiveRecord::Base; end before do ActiveRecord::Base.establish_connection( adapter: 'percona', + original_adapter: db_config['original_adapter'], host: db_config['hostname'], password: db_config['password'], - database: db_config['database'] + database: db_config['database'], + ssl_mode: 'required' ) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1a71be76..43c979d5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,16 +20,24 @@ db_config = Configuration.new +begin + require 'activerecord-trilogy-adapter' +rescue StandardError + puts "'activerecord-trilogy-adapter' not loaded for #{Rails.version}" +end + # Disables/enables the queries log you see in your rails server in dev mode fd = ENV['VERBOSE'] ? STDOUT : '/dev/null' ActiveRecord::Base.logger = Logger.new(fd) ActiveRecord::Base.establish_connection( adapter: 'percona', + original_adapter: db_config['original_adapter'], host: db_config['hostname'], username: db_config['username'], password: db_config['password'], - database: db_config['database'] + database: db_config['database'], + ssl_mode: 'required' ) MIGRATION_FIXTURES = File.expand_path('../fixtures/migrate/', __FILE__)