departure 4.0.1 → 6.3.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.
@@ -44,10 +44,10 @@ module Departure
44
44
  begin
45
45
  loop do
46
46
  IO.select([stdout])
47
- data = stdout.read_nonblock(8)
47
+ data = stdout.read_nonblock(8192)
48
48
  logger.write_no_newline(data)
49
49
  end
50
- rescue EOFError
50
+ rescue EOFError # rubocop:disable Lint/HandleExceptions
51
51
  # noop
52
52
  ensure
53
53
  @status = waith_thr.value
@@ -69,7 +69,7 @@ module Departure
69
69
  # @raise [SignalError] if the spawned process received a signal
70
70
  # @raise [CommandNotFoundError] if pt-online-schema-change can't be found
71
71
  def validate_status!
72
- raise SignalError.new(status) if status.signaled?
72
+ raise SignalError.new(status) if status.signaled? # rubocop:disable Style/RaiseArgs
73
73
  raise CommandNotFoundError if status.exitstatus == COMMAND_NOT_FOUND
74
74
  raise Error, error_message unless status.success?
75
75
  end
@@ -1,11 +1,12 @@
1
1
  module Departure
2
2
  class Configuration
3
- attr_accessor :tmp_path, :global_percona_args
3
+ attr_accessor :tmp_path, :global_percona_args, :enabled_by_default
4
4
 
5
5
  def initialize
6
6
  @tmp_path = '.'.freeze
7
7
  @error_log_filename = 'departure_error.log'.freeze
8
8
  @global_percona_args = nil
9
+ @enabled_by_default = true
9
10
  end
10
11
 
11
12
  def error_log_path
@@ -0,0 +1,9 @@
1
+ module Departure
2
+ class ConnectionBase < ActiveRecord::Base
3
+ def self.establish_connection(config = nil)
4
+ super.tap do
5
+ ActiveRecord::Base.connection_specification_name = connection_specification_name
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,8 @@
1
+ require 'shellwords'
1
2
  module Departure
2
3
  # Holds the parameters of the DB connection and formats them to string
3
4
  class ConnectionDetails
4
-
5
+ DEFAULT_PORT = 3306
5
6
  # Constructor
6
7
  #
7
8
  # @param [Hash] connection parametes as used in #establish_conneciton
@@ -14,7 +15,7 @@ module Departure
14
15
  #
15
16
  # @return [String]
16
17
  def to_s
17
- @to_s ||= "-h #{host} -u #{user} #{password_argument}"
18
+ @to_s ||= "#{host_argument} -P #{port} -u #{user} #{password_argument}"
18
19
  end
19
20
 
20
21
  # TODO: Doesn't the abstract adapter already handle this somehow?
@@ -33,12 +34,23 @@ module Departure
33
34
  # @return [String]
34
35
  def password_argument
35
36
  if password.present?
36
- "-p #{password}"
37
+ %(--password #{Shellwords.escape(password)} )
37
38
  else
38
39
  ''
39
40
  end
40
41
  end
41
42
 
43
+ # Returns the host fragment of the details string, adds ssl options if needed
44
+ #
45
+ # @return [String]
46
+ def host_argument
47
+ host_string = host
48
+ if ssl_ca.present?
49
+ host_string += ";mysql_ssl=1;mysql_ssl_client_ca=#{ssl_ca}"
50
+ end
51
+ "-h \"#{host_string}\""
52
+ end
53
+
42
54
  private
43
55
 
44
56
  attr_reader :connection_data
@@ -66,5 +78,19 @@ module Departure
66
78
  def password
67
79
  ENV.fetch('PERCONA_DB_PASSWORD', connection_data[:password])
68
80
  end
81
+
82
+ # Returns the database's port.
83
+ #
84
+ # @return [String]
85
+ def port
86
+ connection_data.fetch(:port, DEFAULT_PORT)
87
+ end
88
+
89
+ # Returns the database' SSL CA certificate.
90
+ #
91
+ # @return [String]
92
+ def ssl_ca
93
+ connection_data.fetch(:sslca, nil)
94
+ end
69
95
  end
70
96
  end
data/lib/departure/dsn.rb CHANGED
@@ -1,9 +1,7 @@
1
1
  module Departure
2
-
3
2
  # Represents the 'DSN' argument of Percona's pt-online-schema-change
4
3
  # See https://www.percona.com/doc/percona-toolkit/2.0/pt-online-schema-change.html#dsn-options
5
4
  class DSN
6
-
7
5
  # Constructor
8
6
  #
9
7
  # @param database [String, Symbol]
@@ -1,7 +1,7 @@
1
1
  module Departure
2
2
  module LogSanitizers
3
3
  class PasswordSanitizer
4
- PASSWORD_REPLACEMENT = '[filtered_password]'
4
+ PASSWORD_REPLACEMENT = '[filtered_password]'.freeze
5
5
 
6
6
  delegate :password_argument, to: :connection_details
7
7
 
@@ -15,6 +15,7 @@ module Departure
15
15
  end
16
16
 
17
17
  private
18
+
18
19
  attr_accessor :connection_details
19
20
  end
20
21
  end
@@ -4,7 +4,6 @@ module Departure
4
4
  # the from ActiveRecord::Migration because the migration's instance can't be
5
5
  # seen from the connection adapter.
6
6
  class Logger
7
-
8
7
  def initialize(sanitizers)
9
8
  @sanitizers = sanitizers
10
9
  end
@@ -15,7 +14,7 @@ module Departure
15
14
  # @param message [String]
16
15
  # @param subitem [Boolean] whether to show message as a nested log item
17
16
  def say(message, subitem = false)
18
- write "#{subitem ? " ->" : "--"} #{message}"
17
+ write "#{subitem ? ' ->' : '--'} #{message}"
19
18
  end
20
19
 
21
20
  # Outputs the text through the stdout adding a new line at the end
@@ -1,6 +1,5 @@
1
1
  module Departure
2
2
  module LoggerFactory
3
-
4
3
  # Returns the appropriate logger instance for the given configuration. Use
5
4
  # :verbose option to log to the stdout
6
5
  #
@@ -0,0 +1,104 @@
1
+ module Departure
2
+ # Hooks Departure into Rails migrations by replacing the configured database
3
+ # adapter.
4
+ #
5
+ # It also patches ActiveRecord's #migrate method so that it patches LHM
6
+ # first. This will make migrations written with LHM to go through the
7
+ # regular Rails Migration DSL.
8
+ module Migration
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ # Holds the name of the adapter that was configured by the app.
13
+ mattr_accessor :original_adapter
14
+
15
+ # Declare on a per-migration class basis whether or not to use Departure.
16
+ # The default for this attribute is set based on
17
+ # Departure.configuration.enabled_by_default (default true).
18
+ class_attribute :uses_departure
19
+ self.uses_departure = true
20
+
21
+ alias_method :active_record_migrate, :migrate
22
+ remove_method :migrate
23
+ end
24
+
25
+ module ClassMethods
26
+ # Declare `uses_departure!` in the class body of your migration to enable
27
+ # Departure for that migration only when
28
+ # Departure.configuration.enabled_by_default is false.
29
+ def uses_departure!
30
+ self.uses_departure = true
31
+ end
32
+
33
+ # Declare `disable_departure!` in the class body of your migration to
34
+ # disable Departure for that migration only (when
35
+ # Departure.configuration.enabled_by_default is true, the default).
36
+ def disable_departure!
37
+ self.uses_departure = false
38
+ end
39
+ end
40
+
41
+ # Replaces the current connection adapter with the PerconaAdapter and
42
+ # patches LHM, then it continues with the regular migration process.
43
+ #
44
+ # @param direction [Symbol] :up or :down
45
+ def departure_migrate(direction)
46
+ reconnect_with_percona
47
+ include_foreigner if defined?(Foreigner)
48
+
49
+ ::Lhm.migration = self
50
+ active_record_migrate(direction)
51
+ end
52
+
53
+ # Migrate with or without Departure based on uses_departure class
54
+ # attribute.
55
+ def migrate(direction)
56
+ if uses_departure?
57
+ departure_migrate(direction)
58
+ else
59
+ reconnect_without_percona
60
+ active_record_migrate(direction)
61
+ end
62
+ end
63
+
64
+ # Includes the Foreigner's Mysql2Adapter implemention in
65
+ # DepartureAdapter to support foreign keys
66
+ def include_foreigner
67
+ Foreigner::Adapter.safe_include(
68
+ :DepartureAdapter,
69
+ Foreigner::ConnectionAdapters::Mysql2Adapter
70
+ )
71
+ end
72
+
73
+ # Make all connections in the connection pool to use PerconaAdapter
74
+ # instead of the current adapter.
75
+ def reconnect_with_percona
76
+ return if connection_config[:adapter] == 'percona'
77
+ Departure::ConnectionBase.establish_connection(connection_config.merge(adapter: 'percona'))
78
+ end
79
+
80
+ # Reconnect without percona adapter when Departure is disabled but was
81
+ # enabled in a previous migration.
82
+ def reconnect_without_percona
83
+ return unless connection_config[:adapter] == 'percona'
84
+ Departure::ConnectionBase.establish_connection(connection_config.merge(adapter: original_adapter))
85
+ end
86
+
87
+ private
88
+
89
+ # Capture the type of the adapter configured by the app if not already set.
90
+ def connection_config
91
+ configuration_hash.tap do |config|
92
+ self.class.original_adapter ||= config[:adapter]
93
+ end
94
+ end
95
+
96
+ private def configuration_hash
97
+ if ActiveRecord::VERSION::STRING >= '6.1'
98
+ ActiveRecord::Base.connection_db_config.configuration_hash
99
+ else
100
+ ActiveRecord::Base.connection_config
101
+ end
102
+ end
103
+ end
104
+ end
@@ -24,10 +24,10 @@ module Departure
24
24
  #
25
25
  # @param [Option]
26
26
  # @return [Boolean]
27
- def ==(another_option)
28
- name == another_option.name
27
+ def ==(other)
28
+ name == other.name
29
29
  end
30
- alias :eql? :==
30
+ alias eql? ==
31
31
 
32
32
  # Returns the option's hash
33
33
  #
@@ -65,7 +65,7 @@ module Departure
65
65
  def value_as_string
66
66
  if value.nil?
67
67
  ''
68
- elsif value.include?("=")
68
+ elsif value.include?('=')
69
69
  " #{value}"
70
70
  else
71
71
  "=#{value}"
@@ -6,23 +6,16 @@ module Departure
6
6
  class Railtie < Rails::Railtie
7
7
  railtie_name :departure
8
8
 
9
- # It drops all previous database connections and reconnects using this
10
- # PerconaAdapter. By doing this, all later ActiveRecord methods called in
11
- # the migration will use this adapter instead of Mysql2Adapter.
12
- #
13
- # It also patches ActiveRecord's #migrate method so that it patches LHM
14
- # first. This will make migrations written with LHM to go through the
15
- # regular Rails Migration DSL.
16
- initializer 'departure.configure_rails_initialization' do
17
- ActiveSupport.on_load(:active_record) do
18
- Departure.load
19
- end
20
- end
21
-
22
9
  initializer 'departure.configure' do |app|
23
10
  Departure.configure do |config|
24
11
  config.tmp_path = app.paths['tmp'].first
25
12
  end
26
13
  end
14
+
15
+ config.after_initialize do
16
+ Departure.configure do |dc|
17
+ ActiveRecord::Migration.uses_departure = dc.enabled_by_default
18
+ end
19
+ end
27
20
  end
28
21
  end
@@ -1,11 +1,9 @@
1
1
  require 'open3'
2
2
 
3
3
  module Departure
4
-
5
4
  # It executes pt-online-schema-change commands in a new process and gets its
6
5
  # output and status
7
6
  class Runner
8
-
9
7
  # Constructor
10
8
  #
11
9
  # @param logger [#say, #write]
@@ -1,3 +1,3 @@
1
1
  module Departure
2
- VERSION = '4.0.1'.freeze
2
+ VERSION = '6.3.0'.freeze
3
3
  end
data/lib/lhm.rb CHANGED
@@ -4,14 +4,13 @@ require 'lhm/adapter'
4
4
  # while providing a different behaviour. We delegate all LHM's methods to
5
5
  # ActiveRecord so that you don't need to modify your old LHM migrations
6
6
  module Lhm
7
-
8
7
  # Yields an adapter instance so that Lhm migration Dsl methods get
9
8
  # delegated to ActiveRecord::Migration ones instead
10
9
  #
11
10
  # @param table_name [String]
12
11
  # @param _options [Hash]
13
12
  # @param block [Block]
14
- def self.change_table(table_name, _options = {}, &block)
13
+ def self.change_table(table_name, _options = {}, &block) # rubocop:disable Lint/UnusedMethodArgument
15
14
  yield Adapter.new(@migration, table_name)
16
15
  end
17
16
 
@@ -22,4 +21,3 @@ module Lhm
22
21
  @migration = migration
23
22
  end
24
23
  end
25
-
data/lib/lhm/adapter.rb CHANGED
@@ -2,12 +2,10 @@ require 'lhm/column_with_sql'
2
2
  require 'lhm/column_with_type'
3
3
 
4
4
  module Lhm
5
-
6
5
  # Translates Lhm DSL to ActiveRecord's one, so Lhm migrations will now go
7
6
  # through Percona as well, without any modification on the migration's
8
7
  # code
9
8
  class Adapter
10
-
11
9
  # Constructor
12
10
  #
13
11
  # @param migration [ActiveRecord::Migtration]
@@ -79,7 +77,7 @@ module Lhm
79
77
  # @param index_name [String]
80
78
  def add_unique_index(columns, index_name = nil)
81
79
  options = { unique: true }
82
- options.merge!(name: index_name) if index_name
80
+ options.merge!(name: index_name) if index_name # rubocop:disable Performance/RedundantMerge
83
81
 
84
82
  migration.add_index(table_name, columns, options)
85
83
  end
@@ -1,7 +1,6 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module Lhm
4
-
5
4
  # Abstracts the details of a table column definition when specified with a MySQL
6
5
  # column definition string
7
6
  class ColumnWithSql
@@ -53,12 +52,17 @@ module Lhm
53
52
  #
54
53
  # @return [column_factory]
55
54
  def column
56
- cast_type = ActiveRecord::Base.connection.lookup_cast_type(definition)
55
+ cast_type = ActiveRecord::Base.connection.send(:lookup_cast_type, definition)
56
+ metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(
57
+ type: cast_type.type,
58
+ sql_type: definition,
59
+ limit: cast_type.limit
60
+ )
61
+ mysql_metadata = ActiveRecord::ConnectionAdapters::MySQL::TypeMetadata.new(metadata)
57
62
  @column ||= self.class.column_factory.new(
58
63
  name,
59
64
  default_value,
60
- cast_type,
61
- definition,
65
+ mysql_metadata,
62
66
  null_value
63
67
  )
64
68
  end
@@ -1,12 +1,10 @@
1
1
  module Lhm
2
-
3
2
  # Abstracts the details of a table column definition when specified with a type
4
3
  # as a symbol. This is the regular ActiveRecord's #add_column syntax:
5
4
  #
6
5
  # add_column :tablenames, :field, :string
7
6
  #
8
7
  class ColumnWithType
9
-
10
8
  # Constructor
11
9
  #
12
10
  # @param name [String, Symbol]
data/test_database.rb CHANGED
@@ -1,9 +1,11 @@
1
+ require 'active_record'
2
+ require 'active_record/connection_adapters/mysql2_adapter'
3
+
1
4
  # Setups the test database with the schema_migrations table that ActiveRecord
2
5
  # requires for the migrations, plus a table for the Comment model used throught
3
6
  # the tests.
4
7
  #
5
8
  class TestDatabase
6
-
7
9
  # Constructor
8
10
  #
9
11
  # @param config [Hash]
@@ -19,7 +21,7 @@ class TestDatabase
19
21
  drop_and_create_schema_migrations_table
20
22
  end
21
23
 
22
- # Creates the #{database} database and the comments table in it.
24
+ # Creates the #{@database} database and the comments table in it.
23
25
  # Before, it drops both if they already exist
24
26
  def setup_test_database
25
27
  drop_and_create_test_database
@@ -29,7 +31,13 @@ class TestDatabase
29
31
  # Creates the ActiveRecord's schema_migrations table required for
30
32
  # migrations to work. Before, it drops the table if it already exists
31
33
  def drop_and_create_schema_migrations_table
32
- %x(#{mysql_command} "USE #{database}; DROP TABLE IF EXISTS schema_migrations; CREATE TABLE schema_migrations ( version varchar(255) COLLATE utf8_unicode_ci NOT NULL, UNIQUE KEY unique_schema_migrations (version)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci")
34
+ sql = [
35
+ "USE #{@database}",
36
+ 'DROP TABLE IF EXISTS schema_migrations',
37
+ 'CREATE TABLE schema_migrations ( version varchar(255) COLLATE utf8_unicode_ci NOT NULL, UNIQUE KEY unique_schema_migrations (version)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'
38
+ ]
39
+
40
+ run_commands(sql)
33
41
  end
34
42
 
35
43
  private
@@ -37,16 +45,36 @@ class TestDatabase
37
45
  attr_reader :config, :database
38
46
 
39
47
  def drop_and_create_test_database
40
- %x(#{mysql_command} "DROP DATABASE IF EXISTS #{database}; CREATE DATABASE #{database} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci;")
48
+ sql = [
49
+ "DROP DATABASE IF EXISTS #{@database}",
50
+ "CREATE DATABASE #{@database} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci"
51
+ ]
52
+
53
+ run_commands(sql)
41
54
  end
42
55
 
43
56
  def drop_and_create_comments_table
44
- %x(#{mysql_command} "USE #{database}; DROP TABLE IF EXISTS comments; CREATE TABLE comments ( id int(12) NOT NULL AUTO_INCREMENT, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;")
57
+ sql = [
58
+ "USE #{@database}",
59
+ 'DROP TABLE IF EXISTS comments',
60
+ 'CREATE TABLE comments ( id bigint(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'
61
+ ]
62
+
63
+ run_commands(sql)
64
+ end
65
+
66
+ def run_commands(sql)
67
+ conn.execute('START TRANSACTION')
68
+ sql.each { |str| conn.execute(str) }
69
+ conn.execute('COMMIT')
45
70
  end
46
71
 
47
- # Returns the command to run the mysql client. It uses the crendentials from
48
- # the provided config
49
- def mysql_command
50
- "mysql --user=#{config['username']} --password=#{config['password']} -e"
72
+ def conn
73
+ @conn ||= ActiveRecord::Base.mysql2_connection(
74
+ host: @config['hostname'],
75
+ username: @config['username'],
76
+ password: @config['password'],
77
+ reconnect: true
78
+ )
51
79
  end
52
80
  end