departure 6.7.0 → 7.0.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 +4 -4
- data/.github/workflows/test.yml +5 -14
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -1
- data/Appraisals +4 -24
- data/CHANGELOG.md +15 -1
- data/Gemfile +6 -1
- data/Gemfile.lock +108 -86
- data/README.md +8 -0
- data/Rakefile +9 -0
- data/bin/rails +24 -0
- data/departure.gemspec +3 -3
- data/gemfiles/rails_7_0.gemfile +8 -2
- data/gemfiles/rails_7_0.gemfile.lock +182 -131
- data/gemfiles/rails_7_1.gemfile +7 -2
- data/gemfiles/rails_7_1.gemfile.lock +156 -140
- data/gemfiles/{ruby_2.7_rails_6_0.gemfile → rails_7_2.gemfile} +7 -3
- data/gemfiles/rails_7_2.gemfile.lock +288 -0
- data/gemfiles/{rails_6_0.gemfile → rails_8_0.gemfile} +6 -2
- data/gemfiles/rails_8_0.gemfile.lock +285 -0
- data/lib/active_record/connection_adapters/for_alter.rb +4 -17
- data/lib/active_record/connection_adapters/patch_connection_handling.rb +18 -0
- data/lib/active_record/connection_adapters/percona_adapter.rb +9 -56
- data/lib/active_record/connection_adapters/rails_7_2_departure_adapter.rb +215 -0
- data/lib/active_record/connection_adapters/rails_8_0_departure_adapter.rb +293 -0
- data/lib/departure/command.rb +6 -2
- data/lib/departure/configuration.rb +2 -1
- data/lib/departure/migration.rb +1 -5
- data/lib/departure/rails_adapter.rb +146 -0
- data/lib/departure/rails_patches/active_record_migrator_with_advisory_lock_patch.rb +25 -0
- data/lib/departure/runner.rb +24 -4
- data/lib/departure/version.rb +1 -1
- data/lib/departure.rb +2 -5
- data/lib/lhm/column_with_sql.rb +1 -8
- data/test_database.rb +2 -6
- metadata +18 -47
- data/gemfiles/rails_6_0.gemfile.lock +0 -238
- data/gemfiles/rails_6_1.gemfile +0 -10
- data/gemfiles/rails_6_1.gemfile.lock +0 -241
- data/gemfiles/ruby_2.7_rails_6_0.gemfile.lock +0 -239
- data/gemfiles/ruby_2.7_rails_6_1.gemfile +0 -11
- data/gemfiles/ruby_2.7_rails_6_1.gemfile.lock +0 -242
- data/gemfiles/ruby_2.7_rails_7_0.gemfile +0 -11
- data/gemfiles/ruby_2.7_rails_7_0.gemfile.lock +0 -241
- data/gemfiles/ruby_2.7_rails_7_1.gemfile +0 -11
- data/gemfiles/ruby_2.7_rails_7_1.gemfile.lock +0 -275
@@ -1,46 +1,12 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
2
2
|
require 'active_record/connection_adapters/statement_pool'
|
3
3
|
require 'active_record/connection_adapters/mysql2_adapter'
|
4
|
+
require 'active_record/connection_adapters/patch_connection_handling'
|
4
5
|
require 'active_support/core_ext/string/filters'
|
5
6
|
require 'departure'
|
6
7
|
require 'forwardable'
|
7
8
|
|
8
9
|
module ActiveRecord
|
9
|
-
module ConnectionHandling
|
10
|
-
# Establishes a connection to the database that's used by all Active
|
11
|
-
# Record objects.
|
12
|
-
def percona_connection(config)
|
13
|
-
if config[:username].nil?
|
14
|
-
config = config.dup if config.frozen?
|
15
|
-
config[:username] = 'root'
|
16
|
-
end
|
17
|
-
mysql2_connection = mysql2_connection(config)
|
18
|
-
|
19
|
-
connection_details = Departure::ConnectionDetails.new(config)
|
20
|
-
verbose = ActiveRecord::Migration.verbose
|
21
|
-
sanitizers = [
|
22
|
-
Departure::LogSanitizers::PasswordSanitizer.new(connection_details)
|
23
|
-
]
|
24
|
-
percona_logger = Departure::LoggerFactory.build(sanitizers: sanitizers, verbose: verbose)
|
25
|
-
cli_generator = Departure::CliGenerator.new(connection_details)
|
26
|
-
|
27
|
-
runner = Departure::Runner.new(
|
28
|
-
percona_logger,
|
29
|
-
cli_generator,
|
30
|
-
mysql2_connection
|
31
|
-
)
|
32
|
-
|
33
|
-
connection_options = { mysql_adapter: mysql2_connection }
|
34
|
-
|
35
|
-
ConnectionAdapters::DepartureAdapter.new(
|
36
|
-
runner,
|
37
|
-
logger,
|
38
|
-
connection_options,
|
39
|
-
config
|
40
|
-
)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
10
|
module ConnectionAdapters
|
45
11
|
class DepartureAdapter < AbstractMysqlAdapter
|
46
12
|
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } if defined?(initialize_type_map)
|
@@ -133,20 +99,11 @@ module ActiveRecord
|
|
133
99
|
# @param column_name [String, Symbol]
|
134
100
|
# @param options [Hash] optional
|
135
101
|
def add_index(table_name, column_name, options = {})
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
SQL
|
142
|
-
else
|
143
|
-
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, **options)
|
144
|
-
execute <<-SQL.squish
|
145
|
-
ALTER TABLE #{quote_table_name(table_name)}
|
146
|
-
ADD #{index_type} INDEX
|
147
|
-
#{quote_column_name(index_name)} (#{index_columns})#{index_options}
|
148
|
-
SQL
|
149
|
-
end
|
102
|
+
index_definition, = add_index_options(table_name, column_name, **options)
|
103
|
+
execute <<-SQL.squish
|
104
|
+
ALTER TABLE #{quote_table_name(index_definition.table)}
|
105
|
+
ADD #{schema_creation.accept(index_definition)}
|
106
|
+
SQL
|
150
107
|
end
|
151
108
|
|
152
109
|
# Remove the given index from the table.
|
@@ -154,12 +111,8 @@ module ActiveRecord
|
|
154
111
|
# @param table_name [String, Symbol]
|
155
112
|
# @param options [Hash] optional
|
156
113
|
def remove_index(table_name, column_name = nil, **options)
|
157
|
-
if
|
158
|
-
|
159
|
-
index_name = index_name_for_remove(table_name, column_name, options)
|
160
|
-
else
|
161
|
-
index_name = index_name_for_remove(table_name, options)
|
162
|
-
end
|
114
|
+
return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
|
115
|
+
index_name = index_name_for_remove(table_name, column_name, options)
|
163
116
|
|
164
117
|
execute "ALTER TABLE #{quote_table_name(table_name)} DROP INDEX #{quote_column_name(index_name)}"
|
165
118
|
end
|
@@ -200,7 +153,7 @@ module ActiveRecord
|
|
200
153
|
|
201
154
|
attr_reader :mysql_adapter
|
202
155
|
|
203
|
-
if ActiveRecord.version
|
156
|
+
if Departure::RailsAdapter.version_matches?(ActiveRecord.version, '~> 7.1.0')
|
204
157
|
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
205
158
|
log(sql, name, async: async) do
|
206
159
|
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
2
|
+
require 'active_record/connection_adapters/statement_pool'
|
3
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
4
|
+
require 'active_record/connection_adapters/patch_connection_handling'
|
5
|
+
require 'active_support/core_ext/string/filters'
|
6
|
+
require 'departure'
|
7
|
+
require 'forwardable'
|
8
|
+
|
9
|
+
module ActiveRecord
|
10
|
+
module ConnectionAdapters
|
11
|
+
class Rails72DepartureAdapter < AbstractMysqlAdapter
|
12
|
+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } if defined?(initialize_type_map)
|
13
|
+
|
14
|
+
class Column < ActiveRecord::ConnectionAdapters::MySQL::Column
|
15
|
+
def adapter
|
16
|
+
Rails72DepartureAdapter
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class SchemaCreation < ActiveRecord::ConnectionAdapters::MySQL::SchemaCreation
|
21
|
+
def visit_DropForeignKey(name) # rubocop:disable Style/MethodName
|
22
|
+
fk_name =
|
23
|
+
if name =~ /^__(.+)/
|
24
|
+
Regexp.last_match(1)
|
25
|
+
else
|
26
|
+
"_#{name}"
|
27
|
+
end
|
28
|
+
|
29
|
+
"DROP FOREIGN KEY #{fk_name}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
extend Forwardable
|
34
|
+
|
35
|
+
include ForAlterStatements unless method_defined?(:change_column_for_alter)
|
36
|
+
|
37
|
+
ADAPTER_NAME = 'Percona'.freeze
|
38
|
+
|
39
|
+
def self.new_client(config)
|
40
|
+
connection_details = Departure::ConnectionDetails.new(config)
|
41
|
+
verbose = ActiveRecord::Migration.verbose
|
42
|
+
sanitizers = [
|
43
|
+
Departure::LogSanitizers::PasswordSanitizer.new(connection_details)
|
44
|
+
]
|
45
|
+
percona_logger = Departure::LoggerFactory.build(sanitizers: sanitizers, verbose: verbose)
|
46
|
+
cli_generator = Departure::CliGenerator.new(connection_details)
|
47
|
+
|
48
|
+
mysql_adapter = ActiveRecord::ConnectionAdapters::Mysql2Adapter.new(config.merge(adapter: 'mysql2'))
|
49
|
+
|
50
|
+
Departure::Runner.new(
|
51
|
+
percona_logger,
|
52
|
+
cli_generator,
|
53
|
+
mysql_adapter
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(config)
|
58
|
+
super
|
59
|
+
|
60
|
+
@config[:flags] ||= 0
|
61
|
+
|
62
|
+
if @config[:flags].is_a? Array
|
63
|
+
@config[:flags].push 'FOUND_ROWS'
|
64
|
+
else
|
65
|
+
@config[:flags] |= ::Mysql2::Client::FOUND_ROWS
|
66
|
+
end
|
67
|
+
|
68
|
+
@prepared_statements = false
|
69
|
+
end
|
70
|
+
|
71
|
+
def write_query?(sql) # :nodoc:
|
72
|
+
!ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
73
|
+
:desc, :describe, :set, :show, :use
|
74
|
+
).match?(sql)
|
75
|
+
end
|
76
|
+
|
77
|
+
def exec_delete(sql, name, binds)
|
78
|
+
execute(to_sql(sql, binds), name)
|
79
|
+
|
80
|
+
@raw_connection.affected_rows
|
81
|
+
end
|
82
|
+
alias exec_update exec_delete
|
83
|
+
|
84
|
+
def exec_insert(sql, name, binds, pky = nil, sequence_name = nil, returning: nil) # rubocop:disable Lint/UnusedMethodArgument, Metrics/Metrics/ParameterLists
|
85
|
+
execute(to_sql(sql, binds), name)
|
86
|
+
end
|
87
|
+
|
88
|
+
def internal_exec_query(sql, name = 'SQL', _binds = [], **_kwargs) # :nodoc:
|
89
|
+
result = execute(sql, name)
|
90
|
+
fields = result.fields if defined?(result.fields)
|
91
|
+
ActiveRecord::Result.new(fields || [], result.to_a)
|
92
|
+
end
|
93
|
+
alias exec_query internal_exec_query
|
94
|
+
|
95
|
+
# Executes a SELECT query and returns an array of rows. Each row is an
|
96
|
+
# array of field values.
|
97
|
+
|
98
|
+
def select_rows(arel, name = nil, binds = [])
|
99
|
+
select_all(arel, name, binds).rows
|
100
|
+
end
|
101
|
+
|
102
|
+
# Executes a SELECT query and returns an array of record hashes with the
|
103
|
+
# column names as keys and column values as values.
|
104
|
+
def select(sql, name = nil, binds = [], **kwargs)
|
105
|
+
exec_query(sql, name, binds, **kwargs)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns true, as this adapter supports migrations
|
109
|
+
def supports_migrations?
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
def new_column(...)
|
114
|
+
Column.new(...)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Adds a new index to the table
|
118
|
+
#
|
119
|
+
# @param table_name [String, Symbol]
|
120
|
+
# @param column_name [String, Symbol]
|
121
|
+
# @param options [Hash] optional
|
122
|
+
def add_index(table_name, column_name, options = {})
|
123
|
+
index_definition, = add_index_options(table_name, column_name, **options)
|
124
|
+
execute <<-SQL.squish
|
125
|
+
ALTER TABLE #{quote_table_name(index_definition.table)}
|
126
|
+
ADD #{schema_creation.accept(index_definition)}
|
127
|
+
SQL
|
128
|
+
end
|
129
|
+
|
130
|
+
# Remove the given index from the table.
|
131
|
+
#
|
132
|
+
# @param table_name [String, Symbol]
|
133
|
+
# @param options [Hash] optional
|
134
|
+
def remove_index(table_name, column_name = nil, **options)
|
135
|
+
return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
|
136
|
+
|
137
|
+
index_name = index_name_for_remove(table_name, column_name, options)
|
138
|
+
|
139
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP INDEX #{quote_column_name(index_name)}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def schema_creation
|
143
|
+
SchemaCreation.new(self)
|
144
|
+
end
|
145
|
+
|
146
|
+
def change_table(table_name, _options = {})
|
147
|
+
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
|
148
|
+
yield update_table_definition(table_name, recorder)
|
149
|
+
bulk_change_table(table_name, recorder.commands)
|
150
|
+
end
|
151
|
+
|
152
|
+
def full_version
|
153
|
+
get_full_version
|
154
|
+
end
|
155
|
+
|
156
|
+
def get_full_version # rubocop:disable Style/AccessorMethodName
|
157
|
+
return @get_full_version if defined? @get_full_version
|
158
|
+
|
159
|
+
with_raw_connection do |conn|
|
160
|
+
@get_full_version = conn.database_adapter.get_database_version.full_version_string
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def last_inserted_id(result)
|
165
|
+
@raw_connection.database_adapter.send(:last_inserted_id, result)
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
attr_reader :mysql_adapter
|
171
|
+
|
172
|
+
def each_hash(result, &block) # :nodoc:
|
173
|
+
@raw_connection.database_adapter.each_hash(result, &block)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Must return the MySQL error number from the exception, if the exception has an
|
177
|
+
# error number.
|
178
|
+
def error_number(exception)
|
179
|
+
@raw_connection.database_adapter.error_number(exception)
|
180
|
+
end
|
181
|
+
|
182
|
+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
183
|
+
log(sql, name, async: async) do |notification_payload|
|
184
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
185
|
+
sync_timezone_changes(conn)
|
186
|
+
result = conn.query(sql)
|
187
|
+
verified! if allow_retry
|
188
|
+
handle_warnings(sql)
|
189
|
+
if result.is_a? Process::Status
|
190
|
+
notification_payload[:exit_code] = result.exitstatus
|
191
|
+
notification_payload[:exit_pid] = result.pid
|
192
|
+
elsif result.respond_to?(:size)
|
193
|
+
notification_payload[:row_count] = result.size
|
194
|
+
end
|
195
|
+
result
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def connect
|
201
|
+
@raw_connection = self.class.new_client(@config)
|
202
|
+
rescue ConnectionNotEstablished => e
|
203
|
+
raise e.set_pool(@pool)
|
204
|
+
end
|
205
|
+
|
206
|
+
def reconnect
|
207
|
+
@lock.synchronize do
|
208
|
+
@raw_connection&.close
|
209
|
+
@raw_connection = nil
|
210
|
+
connect
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,293 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
2
|
+
require 'active_record/connection_adapters/statement_pool'
|
3
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
4
|
+
require 'active_record/connection_adapters/mysql2/database_statements'
|
5
|
+
require 'active_record/connection_adapters/patch_connection_handling'
|
6
|
+
require 'active_support/core_ext/string/filters'
|
7
|
+
require 'departure'
|
8
|
+
require 'forwardable'
|
9
|
+
|
10
|
+
module ActiveRecord
|
11
|
+
module ConnectionAdapters
|
12
|
+
class Rails80DepartureAdapter < AbstractMysqlAdapter
|
13
|
+
include ActiveRecord::ConnectionAdapters::Mysql2::DatabaseStatements
|
14
|
+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } if defined?(initialize_type_map)
|
15
|
+
|
16
|
+
class Column < ActiveRecord::ConnectionAdapters::MySQL::Column
|
17
|
+
def adapter
|
18
|
+
Rails80DepartureAdapter
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class SchemaCreation < ActiveRecord::ConnectionAdapters::MySQL::SchemaCreation
|
23
|
+
def visit_DropForeignKey(name) # rubocop:disable Naming/MethodName
|
24
|
+
fk_name =
|
25
|
+
if name =~ /^__(.+)/
|
26
|
+
Regexp.last_match(1)
|
27
|
+
else
|
28
|
+
"_#{name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
"DROP FOREIGN KEY #{fk_name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
extend Forwardable
|
36
|
+
|
37
|
+
include ForAlterStatements unless method_defined?(:change_column_for_alter)
|
38
|
+
|
39
|
+
ADAPTER_NAME = 'Percona'.freeze
|
40
|
+
|
41
|
+
def self.new_client(config)
|
42
|
+
connection_details = Departure::ConnectionDetails.new(config)
|
43
|
+
verbose = ActiveRecord::Migration.verbose
|
44
|
+
sanitizers = [
|
45
|
+
Departure::LogSanitizers::PasswordSanitizer.new(connection_details)
|
46
|
+
]
|
47
|
+
percona_logger = Departure::LoggerFactory.build(sanitizers: sanitizers, verbose: verbose)
|
48
|
+
cli_generator = Departure::CliGenerator.new(connection_details)
|
49
|
+
|
50
|
+
mysql_adapter = ActiveRecord::ConnectionAdapters::Mysql2Adapter.new(config.merge(adapter: 'mysql2'))
|
51
|
+
|
52
|
+
Departure::Runner.new(
|
53
|
+
percona_logger,
|
54
|
+
cli_generator,
|
55
|
+
mysql_adapter
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(config)
|
60
|
+
super
|
61
|
+
|
62
|
+
@config[:flags] ||= 0
|
63
|
+
|
64
|
+
if @config[:flags].is_a? Array
|
65
|
+
@config[:flags].push 'FOUND_ROWS'
|
66
|
+
else
|
67
|
+
@config[:flags] |= ::Mysql2::Client::FOUND_ROWS
|
68
|
+
end
|
69
|
+
|
70
|
+
@prepared_statements = false
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_query?(sql) # :nodoc:
|
74
|
+
!ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
75
|
+
:desc, :describe, :set, :show, :use
|
76
|
+
).match?(sql)
|
77
|
+
end
|
78
|
+
|
79
|
+
def exec_delete(sql, name, binds)
|
80
|
+
execute(to_sql(sql, binds), name)
|
81
|
+
|
82
|
+
@raw_connection.affected_rows
|
83
|
+
end
|
84
|
+
alias exec_update exec_delete
|
85
|
+
|
86
|
+
def exec_insert(sql, name, binds, pky = nil, sequence_name = nil, returning: nil) # rubocop:disable Lint/UnusedMethodArgument, Metrics/ParameterLists
|
87
|
+
execute(to_sql(sql, binds), name)
|
88
|
+
end
|
89
|
+
|
90
|
+
def internal_exec_query(sql, name = 'SQL', _binds = [], **_kwargs) # :nodoc:
|
91
|
+
result = execute(sql, name)
|
92
|
+
fields = result.fields if defined?(result.fields)
|
93
|
+
ActiveRecord::Result.new(fields || [], result.to_a)
|
94
|
+
end
|
95
|
+
alias exec_query internal_exec_query
|
96
|
+
|
97
|
+
# Executes a SELECT query and returns an array of rows. Each row is an
|
98
|
+
# array of field values.
|
99
|
+
|
100
|
+
def select_rows(arel, name = nil, binds = [])
|
101
|
+
select_all(arel, name, binds).rows
|
102
|
+
end
|
103
|
+
|
104
|
+
# Executes a SELECT query and returns an array of record hashes with the
|
105
|
+
# column names as keys and column values as values.
|
106
|
+
def select(sql, name = nil, binds = [], **kwargs)
|
107
|
+
exec_query(sql, name, binds, **kwargs)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns true, as this adapter supports migrations
|
111
|
+
def supports_migrations?
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
def new_column(...)
|
116
|
+
Column.new(...)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Adds a new index to the table
|
120
|
+
#
|
121
|
+
# @param table_name [String, Symbol]
|
122
|
+
# @param column_name [String, Symbol]
|
123
|
+
# @param options [Hash] optional
|
124
|
+
def add_index(table_name, column_name, options = {})
|
125
|
+
index_definition, = add_index_options(table_name, column_name, **options)
|
126
|
+
execute <<-SQL.squish
|
127
|
+
ALTER TABLE #{quote_table_name(index_definition.table)}
|
128
|
+
ADD #{schema_creation.accept(index_definition)}
|
129
|
+
SQL
|
130
|
+
end
|
131
|
+
|
132
|
+
# Remove the given index from the table.
|
133
|
+
#
|
134
|
+
# @param table_name [String, Symbol]
|
135
|
+
# @param options [Hash] optional
|
136
|
+
def remove_index(table_name, column_name = nil, **options)
|
137
|
+
return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
|
138
|
+
|
139
|
+
index_name = index_name_for_remove(table_name, column_name, options)
|
140
|
+
|
141
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP INDEX #{quote_column_name(index_name)}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def schema_creation
|
145
|
+
SchemaCreation.new(self)
|
146
|
+
end
|
147
|
+
|
148
|
+
def change_table(table_name, _options = {})
|
149
|
+
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
|
150
|
+
yield update_table_definition(table_name, recorder)
|
151
|
+
bulk_change_table(table_name, recorder.commands)
|
152
|
+
end
|
153
|
+
|
154
|
+
def full_version
|
155
|
+
get_full_version
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_full_version # rubocop:disable Naming/AccessorMethodName
|
159
|
+
return @get_full_version if defined? @get_full_version
|
160
|
+
|
161
|
+
with_raw_connection do |conn|
|
162
|
+
@get_full_version = conn.database_adapter.get_database_version.full_version_string
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def last_inserted_id(result)
|
167
|
+
@raw_connection.database_adapter.send(:last_inserted_id, result)
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
attr_reader :mysql_adapter
|
173
|
+
|
174
|
+
def each_hash(result, &block) # :nodoc:
|
175
|
+
@raw_connection.database_adapter.each_hash(result, &block)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Must return the MySQL error number from the exception, if the exception has an
|
179
|
+
# error number.
|
180
|
+
def error_number(exception)
|
181
|
+
@raw_connection.database_adapter.error_number(exception)
|
182
|
+
end
|
183
|
+
|
184
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/ParameterLists
|
185
|
+
def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false,
|
186
|
+
materialize_transactions: true, batch: false)
|
187
|
+
type_casted_binds = type_casted_binds(binds)
|
188
|
+
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
|
189
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
190
|
+
perform_query(conn, sql, binds, type_casted_binds, prepare: prepare,
|
191
|
+
notification_payload: notification_payload, batch: batch)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
197
|
+
reset_multi_statement = if batch && !multi_statements_enabled?
|
198
|
+
raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
# Make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
203
|
+
# made since we established the connection
|
204
|
+
raw_connection.query_options[:database_timezone] = default_timezone
|
205
|
+
|
206
|
+
result = nil
|
207
|
+
if binds.nil? || binds.empty?
|
208
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
209
|
+
result = raw_connection.query(sql)
|
210
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
211
|
+
# As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
|
212
|
+
# from that same connection was GCed while `#query` released the GVL.
|
213
|
+
# By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
|
214
|
+
# of hitting the bug.
|
215
|
+
|
216
|
+
# THIS IS THE CORE CHANGES 1 related to size
|
217
|
+
# We will sometimes have a process exit code instead of a result from executing
|
218
|
+
@affected_rows_before_warnings = result.try(:size) || raw_connection.affected_rows
|
219
|
+
end
|
220
|
+
elsif prepare
|
221
|
+
|
222
|
+
stmt = @statements[sql] ||= raw_connection.prepare(sql)
|
223
|
+
begin
|
224
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
225
|
+
result = stmt.execute(*type_casted_binds)
|
226
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
227
|
+
end
|
228
|
+
rescue ::Mysql2::Error
|
229
|
+
@statements.delete(sql)
|
230
|
+
raise
|
231
|
+
end
|
232
|
+
else
|
233
|
+
stmt = raw_connection.prepare(sql)
|
234
|
+
|
235
|
+
begin
|
236
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
237
|
+
result = stmt.execute(*type_casted_binds)
|
238
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
239
|
+
end
|
240
|
+
|
241
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
242
|
+
# by eagerly closing uncached prepared statements, we also reduce the chances of
|
243
|
+
# that bug happening. It can still happen if `#execute` is used as we have no callback
|
244
|
+
# to eagerly close the statement.
|
245
|
+
if result
|
246
|
+
result.instance_variable_set(:@_ar_stmt_to_close, stmt)
|
247
|
+
else
|
248
|
+
stmt.close
|
249
|
+
end
|
250
|
+
rescue ::Mysql2::Error
|
251
|
+
stmt.close
|
252
|
+
raise
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
notification_payload[:affected_rows] = @affected_rows_before_warnings
|
257
|
+
|
258
|
+
# THIS IS THE CORE CHANGES 2 related to size
|
259
|
+
notification_payload[:row_count] = result.try(:size) || 0
|
260
|
+
|
261
|
+
if result.is_a? Process::Status
|
262
|
+
notification_payload[:exit_code] = result.exitstatus
|
263
|
+
notification_payload[:exit_pid] = result.pid
|
264
|
+
end
|
265
|
+
|
266
|
+
raw_connection.abandon_results!
|
267
|
+
|
268
|
+
verified!
|
269
|
+
handle_warnings(sql)
|
270
|
+
result
|
271
|
+
ensure
|
272
|
+
if reset_multi_statement && active?
|
273
|
+
raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/ParameterLists
|
277
|
+
|
278
|
+
def connect
|
279
|
+
@raw_connection = self.class.new_client(@config)
|
280
|
+
rescue ConnectionNotEstablished => e
|
281
|
+
raise e.set_pool(@pool)
|
282
|
+
end
|
283
|
+
|
284
|
+
def reconnect
|
285
|
+
@lock.synchronize do
|
286
|
+
@raw_connection&.close
|
287
|
+
@raw_connection = nil
|
288
|
+
connect
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
data/lib/departure/command.rb
CHANGED
@@ -42,7 +42,7 @@ module Departure
|
|
42
42
|
# execution status
|
43
43
|
def run_in_process
|
44
44
|
Open3.popen3(full_command) do |_stdin, stdout, _stderr, waith_thr|
|
45
|
-
begin
|
45
|
+
begin # rubocop:disable Style/RedundantBegin
|
46
46
|
loop do
|
47
47
|
IO.select([stdout])
|
48
48
|
data = stdout.read_nonblock(8192)
|
@@ -83,7 +83,11 @@ module Departure
|
|
83
83
|
#
|
84
84
|
# @return [String]
|
85
85
|
def error_message
|
86
|
-
|
86
|
+
if redirect_stderr
|
87
|
+
File.read(error_log_path)
|
88
|
+
else
|
89
|
+
''
|
90
|
+
end
|
87
91
|
end
|
88
92
|
|
89
93
|
# Logs when the execution started
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Departure
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :tmp_path, :global_percona_args, :enabled_by_default, :redirect_stderr
|
3
|
+
attr_accessor :tmp_path, :global_percona_args, :enabled_by_default, :redirect_stderr,
|
4
|
+
:disable_rails_advisory_lock_patch
|
4
5
|
|
5
6
|
def initialize
|
6
7
|
@tmp_path = '.'.freeze
|
data/lib/departure/migration.rb
CHANGED
@@ -94,11 +94,7 @@ module Departure
|
|
94
94
|
end
|
95
95
|
|
96
96
|
private def configuration_hash
|
97
|
-
|
98
|
-
ActiveRecord::Base.connection_db_config.configuration_hash
|
99
|
-
else
|
100
|
-
ActiveRecord::Base.connection_config
|
101
|
-
end
|
97
|
+
ActiveRecord::Base.connection_db_config.configuration_hash
|
102
98
|
end
|
103
99
|
end
|
104
100
|
end
|