departure 8.0.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f073be830cc9193469fb3d31d99ce910ac32ab2de6838f8ee18fd30e1b5a4f26
4
- data.tar.gz: beaffddbe848d268c6a83051c7b98e294ae06ae489111a8038aed3d836febffd
3
+ metadata.gz: 7c430a93842e6abdfa3f64de47ae938c782d53bbaba75ba532baee057b25d471
4
+ data.tar.gz: 0eeaaeaa8a9d6cf83885ab94b4ad1aae833d73f1fd1f9da87414476f5eede157
5
5
  SHA512:
6
- metadata.gz: 37730fd17a593c457fd02060f1bdf07f8c2feb8c9e9e2ad8bb2e3820c38943b0761efb3ee49b9d6fd2d6dc77949b7f9d2c9d784e12556c0826e7462374a128d1
7
- data.tar.gz: 24dff29fd1900e9fd555edd491e1f6715548ca04ce9b7dce0a455e935c07790a6b9ecd8fad52e68fec51834df51c87057b9f937d741159503d73d32c40f3207a
6
+ metadata.gz: 4e0b56e69008ab1885216db89d106775ef10198819d2da007476820921c8c382c79aba3d63637a99276c321723112796936b03b5b099db3a629bbe80d9da21f4
7
+ data.tar.gz: d9de074bdaa985d016e83ad61745e3c77930e9262bc5dea7e4d7a5b9db75d86c66db9f2929d14f474f91f1ffbb3397a553b6c4e60fc0c5dd2e9800db15a04ee6
@@ -3,7 +3,7 @@ name: Test
3
3
  on: [push, pull_request]
4
4
 
5
5
  jobs:
6
- test:
6
+ test_mysql:
7
7
  strategy:
8
8
  fail-fast: false
9
9
  matrix:
@@ -36,6 +36,43 @@ jobs:
36
36
  run: sudo systemctl start mysql.service
37
37
  - run: bin/setup
38
38
  - run: bundle exec rake
39
+
40
+ test_trilogy:
41
+ strategy:
42
+ fail-fast: false
43
+ matrix:
44
+ ruby:
45
+ - 3.2
46
+ - 3.3
47
+ - 3.4
48
+ gemfile:
49
+ - gemfiles/rails_8_1.gemfile
50
+ env:
51
+ DB_ADAPTER: "trilogy"
52
+ PERCONA_DB_USER: root
53
+ PERCONA_DB_PASSWORD: root
54
+ BUNDLE_GEMFILE: ${{ matrix.gemfile }}
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ - uses: ruby/setup-ruby@v1
59
+ with:
60
+ ruby-version: ${{ matrix.ruby }}
61
+ bundler-cache: true
62
+ - name: "Add Percona GPG key"
63
+ run: sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 9334A25F8507EFA5
64
+ - name: "Add Percona APT repository"
65
+ run: echo "deb http://repo.percona.com/apt `lsb_release -cs` main" | sudo tee -a /etc/apt/sources.list
66
+ - run: sudo apt-get update -qq
67
+ - run: sudo apt-get install percona-toolkit
68
+ - name: Start MySQL server
69
+ run: sudo systemctl start mysql.service
70
+ - name: Configure MySQL for Trilogy
71
+ run: |
72
+ sudo mysql -u root -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root'; FLUSH PRIVILEGES;"
73
+ - run: bin/setup
74
+ - run: bundle exec rake
75
+
39
76
  lint:
40
77
  strategy:
41
78
  fail-fast: false
data/AGENTS.md ADDED
@@ -0,0 +1,75 @@
1
+ # Context
2
+
3
+ Departure is a Ruby gem that wraps Rails ActiveRecord migrations using `ALTER TABLE` statements with `pt-online-schema-change` (Percona Toolkit) so DDL runs online and non-blocking.
4
+
5
+ It must stay aware of how the ActiveRecord API changes across versions and supports all currently supported versions of Rails and Ruby.
6
+
7
+ It supports both the `mysql2` and `trilogy` database adapter gems (trilogy on Rails 8.1+).
8
+
9
+ # Project Layout
10
+
11
+ - `lib/active_record/connection_adapters/` — per-Rails-version connection adapters:
12
+ - `rails_7_2_departure_adapter.rb`
13
+ - `rails_8_0_departure_adapter.rb`
14
+ - `rails_8_1_mysql2_adapter.rb`
15
+ - `rails_8_1_trilogy_adapter.rb`
16
+ - `for_alter.rb`, `patch_connection_handling.rb` — shared behavior
17
+ - `lib/departure/rails_adapter.rb` — version dispatch. `Departure::RailsAdapter.for(ar_version, db_connection_adapter:)` selects the right adapter class. New Rails/adapter combinations are wired in here.
18
+ - `lib/departure/railtie.rb` — Rails integration entry point; calls `RailsAdapter.register_integrations`.
19
+ - `lib/departure/runner.rb`, `cli_generator.rb`, `command.rb` — intercept ALTER statements and shell out to `pt-online-schema-change`.
20
+ - `lib/departure/rails_patches/` — targeted patches against ActiveRecord internals.
21
+ - `lib/lhm/` — LHM DSL compatibility shim.
22
+ - `spec/dummy/` — minimal Rails app used by integration specs.
23
+
24
+ # Adapter Selection
25
+
26
+ 1. If the database config specifies `trilogy` (Rails 8.1+), use the trilogy adapter.
27
+ 2. Otherwise default to `mysql2`.
28
+
29
+ Selection lives in `Departure::RailsAdapter.for`. When adding a new Rails version, add a `V<MAJOR>_<MINOR>_*Adapter` subclass of `BaseAdapter` and update the dispatch logic.
30
+
31
+ # Development
32
+
33
+ - `docker-compose.yml` brings up a MySQL database container and a Rails container with the gem mounted in.
34
+ - The `Appraisal` gem manages Rails version dependencies; configurations live in `Appraisals` and generated gemfiles in `gemfiles/`.
35
+ - Local gems are vendored to `tmp/local_gems` (mounted at `/app/vendor/bundle`) so debugging tools like `pry` can step through ActiveRecord internals.
36
+
37
+ ## Required env vars
38
+ Set by `docker-compose.yml`; if running outside Docker, export them yourself:
39
+
40
+ - `PERCONA_DB_USER`
41
+ - `PERCONA_DB_PASSWORD`
42
+ - `PERCONA_DB_HOST`
43
+ - `PERCONA_DB_NAME`
44
+ - `DB_ADAPTER=trilogy` — only when running against trilogy (Rails 8.1)
45
+
46
+ ## External dependencies
47
+
48
+ - `pt-online-schema-change` from Percona Toolkit must be on `PATH` — the gem shells out to it. CI installs it from the Percona APT repo.
49
+ - MySQL server. Trilogy requires `mysql_native_password` auth, not `caching_sha2_password`. CI runs `ALTER USER ... IDENTIFIED WITH mysql_native_password` followed by `FLUSH PRIVILEGES` before the trilogy job.
50
+
51
+ # Testing
52
+
53
+ - Be sure that changes are valid against both `rubocop` and the `rspec` suites.
54
+ - The supported test matrix is defined in `.github/workflows/test.yml`:
55
+ - **mysql2:** Ruby 3.2 / 3.3 / 3.4 × Rails 7.2 / 8.0 / 8.1
56
+ - **trilogy:** Ruby 3.2 / 3.3 / 3.4 × Rails 8.1 only
57
+ - **lint:** Ruby 3.4 with the Rails 8.1 gemfile
58
+ - Run inside Docker via `appraisal`:
59
+ - Full suite: `docker compose exec rails bundle exec appraisal rails-8-1 bundle exec rspec spec`
60
+ - Single example: `docker compose exec rails bundle exec appraisal rails-8-1 bundle exec rspec spec/path/to_spec.rb:LINE`
61
+ - Trilogy run: prepend `DB_ADAPTER=trilogy` to the rspec command (rails-8-1 only)
62
+ - Lint: `docker compose exec rails bundle exec appraisal rails-8-1 bundle exec rubocop --parallel`
63
+
64
+ # Adding a Rails version
65
+
66
+ 1. Add an `appraise '<rails-x-y>' do ... end` block in `Appraisals`.
67
+ 2. `bundle exec appraisal generate && bundle exec appraisal install`
68
+ 3. Add a matching `V<X>_<Y>_*Adapter` class in `lib/departure/rails_adapter.rb` and a connection-adapter file under `lib/active_record/connection_adapters/`.
69
+ 4. Update the `gemfile:` matrix in `.github/workflows/test.yml`. Add a separate `test_trilogy` entry only if trilogy is supported on that version.
70
+
71
+ # Conventions
72
+
73
+ - Follow the existing adapter naming pattern: `rails_<MAJOR>_<MINOR>_<DRIVER>_adapter.rb` and register through `Departure::RailsAdapter`. Don't introduce a parallel registration path.
74
+ - User-visible changes go in `CHANGELOG.md` (Keep a Changelog format).
75
+ - Release process is documented in `RELEASING.md`.
data/Appraisals CHANGED
@@ -10,4 +10,5 @@ end
10
10
 
11
11
  appraise 'rails-8-1' do
12
12
  gem 'rails', '8.1.1'
13
+ gem 'trilogy'
13
14
  end
data/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ Please follow the format in [Keep a Changelog](http://keepachangelog.com/)
6
6
 
7
7
  ## [NEXT]
8
8
 
9
+ ## [8.1.0] - 2026-05-28
10
+
11
+ - [Add Rails 8.1 Trilogy adapter support](https://github.com/departurerb/departure/pull/132)
12
+
13
+ ## [8.0.2] - 2026-04-30
14
+
15
+ - [Fix Rails multi-database migrations](https://github.com/departurerb/departure/pull/138)
16
+
17
+ ## [8.0.1] - 2025-12-12
18
+
19
+ - [Remove stderr message "Including for_alter statements"](https://github.com/departurerb/departure/pull/136)
20
+
9
21
  ## [8.0.0] - 2025-11-24
10
22
 
11
23
  - Bump [appraisal to 2.5.0](https://github.com/departurerb/departure/pull/129)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- departure (8.0.0)
4
+ departure (8.1.0)
5
5
  activerecord (>= 7.2.0)
6
6
  mysql2 (>= 0.4.0, < 0.6.0)
7
7
  railties (>= 7.2.0)
data/README.md CHANGED
@@ -151,9 +151,16 @@ It's strongly recommended to name it after this gems name, such as
151
151
 
152
152
  All configuration options are configurable from the `Departure.configure` block example below
153
153
 
154
- |Option|Default|What it Controls|
155
- |---|---|---|
156
- |disable_rails_advisory_lock_patch|false|When truthy, disables a patch in at least rails 7.1 and 7.2 where rails throws ConcurrentMigrationErrors due to the inability to release the advisory lock in migrations|
154
+ | Option | Default | What it Controls |
155
+ |-----------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
156
+ | disable_rails_advisory_lock_patch | false | When truthy, disables a patch in at least rails 7.1 and 7.2 where rails throws ConcurrentMigrationErrors due to the inability to release the advisory lock in migrations |
157
+
158
+ ### Trilogy Adapter Support
159
+
160
+ Starting in Rails 8.1 we add support for the use of the trilogy database adapter gem. Logic for selecting an adapter follows this logic
161
+
162
+ 1. If the database configuration specifies 'trilogy' use the trilogy adapter
163
+ 2. Default to mysql2
157
164
 
158
165
  ### Disable on per-migration basis
159
166
 
@@ -213,6 +220,21 @@ When any errors occur, an `ActiveRecord::StatementInvalid` exception is
213
220
  raised and the migration is aborted, as all other ActiveRecord connection
214
221
  adapters.
215
222
 
223
+ ### Rails multi-database applications
224
+
225
+ Rails multi-database applications expect `bin/rails db:migrate` to run
226
+ migrations for every configured database. Rails 8.1 does this through
227
+ `ActiveRecord::Tasks::DatabaseTasks.migrate_all`, which temporarily switches the
228
+ migration connection to each database configuration before running that
229
+ database's migrations.
230
+
231
+ Departure supports that flow by restoring Rails' migration connection identity
232
+ after each migration. During the migration body, Departure can still reconnect
233
+ with the Percona adapter so `ALTER TABLE` statements go through
234
+ `pt-online-schema-change`. After the migration finishes, Rails gets back the
235
+ same `ActiveRecord::Base.connection_specification_name` it had before Departure
236
+ swapped adapters, so the next configured database can be migrated normally.
237
+
216
238
  ### Diagram
217
239
 
218
240
  ```mermaid
@@ -226,7 +248,7 @@ adapters.
226
248
  %% Core Departure Components
227
249
  subgraph "Departure System"
228
250
  RailsAdapter["RailsAdapter<br/>(Version Detection)"]
229
- DepartureAdapter["Rails81DepartureAdapter<br/>(Connection Adapter)"]
251
+ DepartureAdapter["Rails81Mysql2Adapter<br/>(Connection Adapter)"]
230
252
  Runner["Runner<br/>(Query Interceptor)"]
231
253
  Command["Command<br/>(Process Executor)"]
232
254
  CliGenerator["CliGenerator<br/>(Command Builder)"]
data/departure.gemspec CHANGED
@@ -8,9 +8,8 @@ require 'departure/version'
8
8
  Gem::Specification.new do |spec|
9
9
  spec.name = 'departure'
10
10
  spec.version = Departure::VERSION
11
- spec.authors = ['Ilya Zayats', 'Pau Pérez', 'Fran Casas', 'Jorge Morante', 'Enrico Stano', 'Adrian Serafin', 'Kirk Haines', 'Guillermo Iguaran']
12
- spec.email = ['ilya.zayats@redbooth.com', 'pau.perez@redbooth.com', 'nflamel@gmail.com', 'jorge.morante@redbooth.com', 'adrian@softmad.pl', 'wyhaines@gmail.com', 'guilleiguaran@gmail.com']
13
-
11
+ spec.authors = ['Ilya Zayats', 'Pau Pérez', 'Fran Casas', 'Jorge Morante', 'Enrico Stano', 'Adrian Serafin', 'Kirk Haines', 'Guillermo Iguaran', 'Austin Story', 'Douglas Soares de Andrade']
12
+ spec.email = ['ilya.zayats@redbooth.com', 'pau.perez@redbooth.com', 'nflamel@gmail.com', 'jorge.morante@redbooth.com', 'adrian@softmad.pl', 'wyhaines@gmail.com', 'guilleiguaran@gmail.com', 'lonnieastory@gmail.com', 'douglas@51street.dev']
14
13
  spec.summary = %q(pt-online-schema-change runner for ActiveRecord migrations)
15
14
  spec.description = %q(Execute your ActiveRecord migrations with Percona's pt-online-schema-change. Formerly known as Percona Migrator.)
16
15
  spec.homepage = 'https://github.com/departurerb/departure'
@@ -1,16 +1,16 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "base64"
6
- gem "codeclimate-test-reporter", "~> 1.0.3", group: :test, require: nil
7
- gem "lhm"
8
- gem "logger"
9
- gem "mutex_m", require: false
10
- gem "rubocop", "~> 1.74.0", require: false
11
- gem "rubocop-performance", "~> 1.20.2", require: false
12
- gem "zeitwerk", "< 2.7.0"
13
- gem "bigdecimal"
14
- gem "rails", "7.2.2.1"
5
+ gem 'base64'
6
+ gem 'bigdecimal'
7
+ gem 'codeclimate-test-reporter', '~> 1.0.3', group: :test, require: nil
8
+ gem 'lhm'
9
+ gem 'logger'
10
+ gem 'mutex_m', require: false
11
+ gem 'rails', '7.2.2.1'
12
+ gem 'rubocop', '~> 1.74.0', require: false
13
+ gem 'rubocop-performance', '~> 1.20.2', require: false
14
+ gem 'zeitwerk', '< 2.7.0'
15
15
 
16
- gemspec path: "../"
16
+ gemspec path: '../'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- departure (8.0.0)
4
+ departure (8.1.0)
5
5
  activerecord (>= 7.2.0)
6
6
  mysql2 (>= 0.4.0, < 0.6.0)
7
7
  railties (>= 7.2.0)
@@ -1,16 +1,16 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "base64"
6
- gem "codeclimate-test-reporter", "~> 1.0.3", group: :test, require: nil
7
- gem "lhm"
8
- gem "logger"
9
- gem "mutex_m", require: false
10
- gem "rubocop", "~> 1.74.0", require: false
11
- gem "rubocop-performance", "~> 1.20.2", require: false
12
- gem "zeitwerk", "< 2.7.0"
13
- gem "bigdecimal"
14
- gem "rails", "8.0.2.1"
5
+ gem 'base64'
6
+ gem 'bigdecimal'
7
+ gem 'codeclimate-test-reporter', '~> 1.0.3', group: :test, require: nil
8
+ gem 'lhm'
9
+ gem 'logger'
10
+ gem 'mutex_m', require: false
11
+ gem 'rails', '8.0.2.1'
12
+ gem 'rubocop', '~> 1.74.0', require: false
13
+ gem 'rubocop-performance', '~> 1.20.2', require: false
14
+ gem 'zeitwerk', '< 2.7.0'
15
15
 
16
- gemspec path: "../"
16
+ gemspec path: '../'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- departure (8.0.0)
4
+ departure (8.1.0)
5
5
  activerecord (>= 7.2.0)
6
6
  mysql2 (>= 0.4.0, < 0.6.0)
7
7
  railties (>= 7.2.0)
@@ -1,15 +1,16 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "base64"
6
- gem "codeclimate-test-reporter", "~> 1.0.3", group: :test, require: nil
7
- gem "lhm"
8
- gem "logger"
9
- gem "mutex_m", require: false
10
- gem "rubocop", "~> 1.74.0", require: false
11
- gem "rubocop-performance", "~> 1.20.2", require: false
12
- gem "zeitwerk", "< 2.7.0"
13
- gem "rails", "8.1.1"
5
+ gem 'base64'
6
+ gem 'codeclimate-test-reporter', '~> 1.0.3', group: :test, require: nil
7
+ gem 'lhm'
8
+ gem 'logger'
9
+ gem 'mutex_m', require: false
10
+ gem 'rails', '8.1.1'
11
+ gem 'rubocop', '~> 1.74.0', require: false
12
+ gem 'rubocop-performance', '~> 1.20.2', require: false
13
+ gem 'trilogy'
14
+ gem 'zeitwerk', '< 2.7.0'
14
15
 
15
- gemspec path: "../"
16
+ gemspec path: '../'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- departure (8.0.0)
4
+ departure (8.1.0)
5
5
  activerecord (>= 7.2.0)
6
6
  mysql2 (>= 0.4.0, < 0.6.0)
7
7
  railties (>= 7.2.0)
@@ -270,6 +270,7 @@ GEM
270
270
  stringio (3.1.7)
271
271
  thor (1.4.0)
272
272
  timeout (0.4.4)
273
+ trilogy (2.9.0)
273
274
  tsort (0.2.0)
274
275
  tzinfo (2.0.6)
275
276
  concurrent-ruby (~> 1.0)
@@ -310,6 +311,7 @@ DEPENDENCIES
310
311
  rspec-its (~> 1.2)
311
312
  rubocop (~> 1.74.0)
312
313
  rubocop-performance (~> 1.20.2)
314
+ trilogy
313
315
  zeitwerk (< 2.7.0)
314
316
 
315
317
  BUNDLED WITH
@@ -1,12 +1,6 @@
1
1
  require 'active_record/connection_adapters/mysql/schema_statements'
2
2
 
3
3
  module ForAlterStatements
4
- class << self
5
- def included(_)
6
- STDERR.puts 'Including for_alter statements'
7
- end
8
- end
9
-
10
4
  def bulk_change_table(table_name, operations) #:nodoc:
11
5
  sqls = operations.flat_map do |command, args|
12
6
  table = args.shift
@@ -7,12 +7,13 @@ module ActiveRecord
7
7
  # Establishes a connection to the database that's used by all Active
8
8
  # Record objects.
9
9
  def percona_connection(config)
10
- if config[:username].nil?
11
- config = config.dup if config.frozen?
12
- config[:username] = 'root'
13
- end
10
+ config = config.dup
11
+ original_adapter = config.delete(:departure_original_adapter)
12
+ config[:username] ||= 'root'
14
13
 
15
- Departure::RailsAdapter.for_current.create_connection_adapter(**config)
14
+ Departure::RailsAdapter
15
+ .for_current(db_connection_adapter: original_adapter)
16
+ .create_connection_adapter(**config)
16
17
  end
17
18
  end
18
19
  end
@@ -1,25 +1,6 @@
1
- require 'active_record/connection_adapters/abstract_mysql_adapter'
2
- require 'active_record/connection_adapters/mysql2_adapter'
3
- require 'active_record/connection_adapters/patch_connection_handling'
4
- require 'departure'
5
- require 'forwardable'
6
-
7
1
  module ActiveRecord
8
2
  module ConnectionAdapters
9
- class Rails81DepartureAdapter < ActiveRecord::ConnectionAdapters::Mysql2Adapter
10
- TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } if defined?(initialize_type_map)
11
-
12
- class Column < ActiveRecord::ConnectionAdapters::MySQL::Column
13
- def adapter
14
- Rails81DepartureAdapter
15
- end
16
- end
17
-
18
- # https://github.com/departurerb/departure/commit/f178ca26cd3befa1c68301d3b57810f8cdcff9eb
19
- # For `DROP FOREIGN KEY constraint_name` with pt-online-schema-change requires specifying `_constraint_name`
20
- # rather than the real constraint_name due to to a limitation in MySQL
21
- # pt-online-schema-change adds a leading underscore to foreign key constraint names when creating the new table.
22
- # https://www.percona.com/blog/2017/03/21/dropping-foreign-key-constraint-using-pt-online-schema-change-2/
3
+ module Rails81AdapterBehavior
23
4
  class SchemaCreation < ActiveRecord::ConnectionAdapters::MySQL::SchemaCreation
24
5
  def visit_DropForeignKey(name) # rubocop:disable Naming/MethodName
25
6
  fk_name =
@@ -33,16 +14,18 @@ module ActiveRecord
33
14
  end
34
15
  end
35
16
 
36
- extend Forwardable
37
-
38
- include ForAlterStatements unless method_defined?(:change_column_for_alter)
39
-
40
- ADAPTER_NAME = 'Percona'.freeze
17
+ def self.included(adapter_class)
18
+ adapter_class.const_set(:SchemaCreation, SchemaCreation)
19
+ adapter_class.extend(ClassMethods)
20
+ adapter_class.include ForAlterStatements unless adapter_class.method_defined?(:change_column_for_alter)
21
+ end
41
22
 
42
- def self.new_client(config)
43
- original_client = super
23
+ module ClassMethods
24
+ def new_client(config)
25
+ original_client = super
44
26
 
45
- Departure::DbClient.new(config, original_client)
27
+ Departure::DbClient.new(config, original_client)
28
+ end
46
29
  end
47
30
 
48
31
  # add_index is modified from the underlying mysql adapter implementation to ensure we add ALTER TABLE to it
@@ -64,13 +47,11 @@ module ActiveRecord
64
47
  end
65
48
 
66
49
  def schema_creation
67
- SchemaCreation.new(self)
50
+ self.class::SchemaCreation.new(self)
68
51
  end
69
52
 
70
53
  private
71
54
 
72
- attr_reader :mysql_adapter
73
-
74
55
  # rubocop:disable Metrics/ParameterLists
75
56
  def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
76
57
  return raw_connection.send_to_pt_online_schema_change(sql) if raw_connection.alter_statement?(sql)
@@ -0,0 +1,23 @@
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
2
+ require 'active_record/connection_adapters/mysql2_adapter'
3
+ require 'active_record/connection_adapters/patch_connection_handling'
4
+ require 'departure'
5
+ require 'active_record/connection_adapters/rails_8_1_adapter_behavior'
6
+
7
+ module ActiveRecord
8
+ module ConnectionAdapters
9
+ class Rails81Mysql2Adapter < ActiveRecord::ConnectionAdapters::Mysql2Adapter
10
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } if defined?(initialize_type_map)
11
+
12
+ class Column < ActiveRecord::ConnectionAdapters::MySQL::Column
13
+ def adapter
14
+ Rails81Mysql2Adapter
15
+ end
16
+ end
17
+
18
+ ADAPTER_NAME = 'Percona'.freeze
19
+
20
+ include Rails81AdapterBehavior
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
2
+ require 'active_record/connection_adapters/trilogy_adapter'
3
+ require 'active_record/connection_adapters/patch_connection_handling'
4
+ require 'departure'
5
+ require 'active_record/connection_adapters/rails_8_1_adapter_behavior'
6
+
7
+ module ActiveRecord
8
+ module ConnectionAdapters
9
+ class Rails81TrilogyAdapter < ActiveRecord::ConnectionAdapters::TrilogyAdapter
10
+ class Column < ActiveRecord::ConnectionAdapters::MySQL::Column
11
+ def adapter
12
+ Rails81TrilogyAdapter
13
+ end
14
+ end
15
+
16
+ ADAPTER_NAME = 'Percona'.freeze
17
+
18
+ include Rails81AdapterBehavior
19
+ end
20
+ end
21
+ end
@@ -8,6 +8,8 @@ module Departure
8
8
  module Migration
9
9
  extend ActiveSupport::Concern
10
10
 
11
+ DEPARTURE_ADAPTERS = %w[percona percona_trilogy].freeze
12
+
11
13
  included do
12
14
  # Holds the name of the adapter that was configured by the app.
13
15
  mattr_accessor :original_adapter
@@ -53,11 +55,13 @@ module Departure
53
55
  # Migrate with or without Departure based on uses_departure class
54
56
  # attribute.
55
57
  def migrate(direction)
56
- if uses_departure?
57
- departure_migrate(direction)
58
- else
59
- reconnect_without_percona
60
- active_record_migrate(direction)
58
+ with_restored_connection_specification_name do
59
+ if uses_departure?
60
+ departure_migrate(direction)
61
+ else
62
+ reconnect_without_percona
63
+ active_record_migrate(direction)
64
+ end
61
65
  end
62
66
  end
63
67
 
@@ -73,15 +77,29 @@ module Departure
73
77
  # Make all connections in the connection pool to use PerconaAdapter
74
78
  # instead of the current adapter.
75
79
  def reconnect_with_percona
76
- return if connection_config[:adapter] == 'percona'
77
- Departure::ConnectionBase.establish_connection(connection_config.merge(adapter: 'percona'))
80
+ config = connection_config
81
+ return if departure_adapter_config?(config)
82
+
83
+ departure_adapter = Departure::RailsAdapter.for_current(db_connection_adapter: config[:adapter])
84
+ Departure::ConnectionBase.establish_connection(
85
+ config.merge(
86
+ adapter: departure_adapter.departure_adapter_name,
87
+ departure_original_adapter: config[:adapter]
88
+ )
89
+ )
78
90
  end
79
91
 
80
92
  # Reconnect without percona adapter when Departure is disabled but was
81
93
  # enabled in a previous migration.
82
94
  def reconnect_without_percona
83
- return unless connection_config[:adapter] == 'percona'
84
- Departure::OriginalAdapterConnection.establish_connection(connection_config.merge(adapter: original_adapter))
95
+ config = connection_config
96
+ return unless departure_adapter_config?(config)
97
+
98
+ Departure::OriginalAdapterConnection.establish_connection(
99
+ config
100
+ .except(:departure_original_adapter)
101
+ .merge(adapter: config[:departure_original_adapter] || original_adapter)
102
+ )
85
103
  end
86
104
 
87
105
  private
@@ -89,12 +107,24 @@ module Departure
89
107
  # Capture the type of the adapter configured by the app if not already set.
90
108
  def connection_config
91
109
  configuration_hash.tap do |config|
92
- self.class.original_adapter ||= config[:adapter]
110
+ self.class.original_adapter ||= config[:departure_original_adapter] || config[:adapter]
93
111
  end
94
112
  end
95
113
 
114
+ private def departure_adapter_config?(config)
115
+ DEPARTURE_ADAPTERS.include?(config[:adapter])
116
+ end
117
+
96
118
  private def configuration_hash
97
119
  ActiveRecord::Base.connection_db_config.configuration_hash
98
120
  end
121
+
122
+ private def with_restored_connection_specification_name
123
+ connection_specification_name = ActiveRecord::Base.connection_specification_name
124
+
125
+ yield
126
+ ensure
127
+ ActiveRecord::Base.connection_specification_name = connection_specification_name
128
+ end
99
129
  end
100
130
  end
@@ -10,7 +10,13 @@ module Departure
10
10
  class MustImplementError < StandardError; end
11
11
 
12
12
  class << self
13
+ def register_integrations(**args)
14
+ for_current(**args).register_integrations
15
+ end
16
+
13
17
  def version_matches?(version_string, compatibility_string = current_version::STRING)
18
+ raise "Invalid Gem Version: '#{version_string}'" unless Gem::Version.correct?(version_string)
19
+
14
20
  requirement = Gem::Requirement.new(compatibility_string)
15
21
  requirement.satisfied_by?(Gem::Version.new(version_string))
16
22
  end
@@ -19,13 +25,19 @@ module Departure
19
25
  ActiveRecord::VERSION
20
26
  end
21
27
 
22
- def for_current
23
- self.for(current_version)
28
+ def for_current(**args)
29
+ self.for(current_version, **args)
24
30
  end
25
31
 
26
- def for(ar_version)
32
+ # rubocop:disable Metrics/PerceivedComplexity
33
+ def for(ar_version, db_connection_adapter: nil)
34
+ # rubocop:enable Metrics/PerceivedComplexity
27
35
  if ar_version::MAJOR == 8 && ar_version::MINOR.positive?
28
- V8_1_Adapter
36
+ if db_connection_adapter == 'trilogy'
37
+ V8_1_TrilogyAdapter
38
+ else
39
+ V8_1_Mysql2Adapter
40
+ end
29
41
  elsif ar_version::MAJOR == 8
30
42
  V8_0_Adapter
31
43
  elsif ar_version::MAJOR >= 7 && ar_version::MINOR >= 2
@@ -47,6 +59,10 @@ module Departure
47
59
  raise MustImplementError, 'adapter must implement create_connection_adapter'
48
60
  end
49
61
 
62
+ def departure_adapter_name
63
+ 'percona'
64
+ end
65
+
50
66
  # https://github.com/rails/rails/commit/9ad36e067222478090b36a985090475bb03e398c#diff-de807ece2205a84c0e3de66b0e5ab831325d567893b8b88ce0d6e9d498f923d1
51
67
  # Rails Column arity changed to require cast_type in position 2 which required us introducing this indirection
52
68
  def new_sql_column(name:,
@@ -69,14 +85,12 @@ module Departure
69
85
  require 'active_record/connection_adapters/rails_7_2_departure_adapter'
70
86
  require 'departure/rails_patches/active_record_migrator_with_advisory_lock_patch'
71
87
 
72
- ActiveSupport.on_load(:active_record) do
73
- ActiveRecord::Migration.class_eval do
74
- include Departure::Migration
75
- end
76
-
77
- ActiveRecord::Migrator.prepend Departure::RailsPatches::ActiveRecordMigratorWithAdvisoryLockPatch
88
+ ActiveRecord::Migration.class_eval do
89
+ include Departure::Migration
78
90
  end
79
91
 
92
+ ActiveRecord::Migrator.prepend Departure::RailsPatches::ActiveRecordMigratorWithAdvisoryLockPatch
93
+
80
94
  ActiveRecord::ConnectionAdapters.register 'percona',
81
95
  'ActiveRecord::ConnectionAdapters::Rails72DepartureAdapter',
82
96
  'active_record/connection_adapters/rails_7_2_departure_adapter'
@@ -98,14 +112,12 @@ module Departure
98
112
  require 'active_record/connection_adapters/rails_8_0_departure_adapter'
99
113
  require 'departure/rails_patches/active_record_migrator_with_advisory_lock_patch'
100
114
 
101
- ActiveSupport.on_load(:active_record) do
102
- ActiveRecord::Migration.class_eval do
103
- include Departure::Migration
104
- end
105
-
106
- ActiveRecord::Migrator.prepend Departure::RailsPatches::ActiveRecordMigratorWithAdvisoryLockPatch
115
+ ActiveRecord::Migration.class_eval do
116
+ include Departure::Migration
107
117
  end
108
118
 
119
+ ActiveRecord::Migrator.prepend Departure::RailsPatches::ActiveRecordMigratorWithAdvisoryLockPatch
120
+
109
121
  ActiveRecord::ConnectionAdapters.register 'percona',
110
122
  'ActiveRecord::ConnectionAdapters::Rails80DepartureAdapter',
111
123
  'active_record/connection_adapters/rails_8_0_departure_adapter'
@@ -121,27 +133,15 @@ module Departure
121
133
  end
122
134
  end
123
135
 
124
- class V8_1_Adapter < BaseAdapter # rubocop:disable Naming/ClassAndModuleCamelCase
136
+ class V8_1_Mysql2Adapter < BaseAdapter # rubocop:disable Naming/ClassAndModuleCamelCase
125
137
  class << self
126
138
  def register_integrations
127
- require 'active_record/connection_adapters/rails_8_1_departure_adapter'
128
- require 'departure/rails_patches/active_record_migrator_with_advisory_lock_patch'
129
-
130
- ActiveSupport.on_load(:active_record) do
131
- ActiveRecord::Migration.class_eval do
132
- include Departure::Migration
133
- end
134
-
135
- ActiveRecord::Migrator.prepend Departure::RailsPatches::ActiveRecordMigratorWithAdvisoryLockPatch
136
- end
137
-
138
- ActiveRecord::ConnectionAdapters.register 'percona',
139
- 'ActiveRecord::ConnectionAdapters::Rails81DepartureAdapter',
140
- 'active_record/connection_adapters/rails_8_1_departure_adapter'
139
+ require 'active_record/connection_adapters/rails_8_1_mysql2_adapter'
140
+ register_rails_8_1_integrations
141
141
  end
142
142
 
143
143
  def create_connection_adapter(**config)
144
- ActiveRecord::ConnectionAdapters::Rails81DepartureAdapter.new(config)
144
+ ActiveRecord::ConnectionAdapters::Rails81Mysql2Adapter.new(config)
145
145
  end
146
146
 
147
147
  # rubocop:disable Metrics/ParameterLists
@@ -160,6 +160,42 @@ module Departure
160
160
  def sql_column
161
161
  ::ActiveRecord::ConnectionAdapters::MySQL::Column
162
162
  end
163
+
164
+ private
165
+
166
+ def register_rails_8_1_integrations
167
+ require 'departure/rails_patches/active_record_migrator_with_advisory_lock_patch'
168
+
169
+ ActiveRecord::Migration.class_eval do
170
+ include Departure::Migration
171
+ end
172
+
173
+ ActiveRecord::Migrator.prepend Departure::RailsPatches::ActiveRecordMigratorWithAdvisoryLockPatch
174
+
175
+ ActiveRecord::ConnectionAdapters.register 'percona',
176
+ 'ActiveRecord::ConnectionAdapters::Rails81Mysql2Adapter',
177
+ 'active_record/connection_adapters/rails_8_1_mysql2_adapter'
178
+ ActiveRecord::ConnectionAdapters.register 'percona_trilogy',
179
+ 'ActiveRecord::ConnectionAdapters::Rails81TrilogyAdapter',
180
+ 'active_record/connection_adapters/rails_8_1_trilogy_adapter'
181
+ end
182
+ end
183
+ end
184
+
185
+ class V8_1_TrilogyAdapter < V8_1_Mysql2Adapter # rubocop:disable Naming/ClassAndModuleCamelCase
186
+ class << self
187
+ def register_integrations
188
+ require 'active_record/connection_adapters/rails_8_1_trilogy_adapter'
189
+ register_rails_8_1_integrations
190
+ end
191
+
192
+ def create_connection_adapter(**config)
193
+ ActiveRecord::ConnectionAdapters::Rails81TrilogyAdapter.new(config)
194
+ end
195
+
196
+ def departure_adapter_name
197
+ 'percona_trilogy'
198
+ end
163
199
  end
164
200
  end
165
201
  end
@@ -10,6 +10,8 @@ module Departure
10
10
  Departure.configure do |config|
11
11
  config.tmp_path = app.paths['tmp'].first
12
12
  end
13
+
14
+ Departure::RailsAdapter.register_integrations
13
15
  end
14
16
 
15
17
  config.after_initialize do
@@ -1,3 +1,3 @@
1
1
  module Departure
2
- VERSION = '8.0.0'.freeze
2
+ VERSION = '8.1.0'.freeze
3
3
  end
data/lib/departure.rb CHANGED
@@ -23,8 +23,6 @@ require 'departure/railtie' if defined?(Rails)
23
23
  # We need the OS not to buffer the IO to see pt-osc's output while migrating
24
24
  $stdout.sync = true
25
25
 
26
- Departure::RailsAdapter.for_current.register_integrations
27
-
28
26
  module Departure
29
27
  class << self
30
28
  attr_accessor :configuration
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: departure
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.0
4
+ version: 8.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ilya Zayats
@@ -12,6 +12,8 @@ authors:
12
12
  - Adrian Serafin
13
13
  - Kirk Haines
14
14
  - Guillermo Iguaran
15
+ - Austin Story
16
+ - Douglas Soares de Andrade
15
17
  bindir: bin
16
18
  cert_chain: []
17
19
  date: 1980-01-02 00:00:00.000000000 Z
@@ -164,6 +166,8 @@ email:
164
166
  - adrian@softmad.pl
165
167
  - wyhaines@gmail.com
166
168
  - guilleiguaran@gmail.com
169
+ - lonnieastory@gmail.com
170
+ - douglas@51street.dev
167
171
  executables: []
168
172
  extensions: []
169
173
  extra_rdoc_files: []
@@ -175,6 +179,7 @@ files:
175
179
  - ".rspec"
176
180
  - ".rubocop.yml"
177
181
  - ".rubocop_todo.yml"
182
+ - AGENTS.md
178
183
  - Appraisals
179
184
  - CHANGELOG.md
180
185
  - CODE_OF_CONDUCT.md
@@ -203,7 +208,9 @@ files:
203
208
  - lib/active_record/connection_adapters/patch_connection_handling.rb
204
209
  - lib/active_record/connection_adapters/rails_7_2_departure_adapter.rb
205
210
  - lib/active_record/connection_adapters/rails_8_0_departure_adapter.rb
206
- - lib/active_record/connection_adapters/rails_8_1_departure_adapter.rb
211
+ - lib/active_record/connection_adapters/rails_8_1_adapter_behavior.rb
212
+ - lib/active_record/connection_adapters/rails_8_1_mysql2_adapter.rb
213
+ - lib/active_record/connection_adapters/rails_8_1_trilogy_adapter.rb
207
214
  - lib/departure.rb
208
215
  - lib/departure/alter_argument.rb
209
216
  - lib/departure/cli_generator.rb