lib/strong_migrations/checker.rb in strong_migrations-0.5.1 vs lib/strong_migrations/checker.rb in strong_migrations-0.6.0
- old
+ new
@@ -4,10 +4,11 @@
def initialize(migration)
@migration = migration
@new_tables = []
@safe = false
+ @timeouts_set = false
end
def safety_assured
previous_value = @safe
begin
@@ -17,10 +18,12 @@
@safe = previous_value
end
end
def perform(method, *args)
+ set_timeouts
+
unless safe?
case method
when :remove_column, :remove_columns, :remove_timestamps, :remove_reference, :remove_belongs_to
columns =
case method
@@ -75,11 +78,11 @@
when :add_column
table, column, type, options = args
options ||= {}
default = options[:default]
- if !default.nil? && !(postgresql? && postgresql_version >= 110000)
+ if !default.nil? && !((postgresql? && postgresql_version >= Gem::Version.new("11")) || (mysql? && mysql_version >= Gem::Version.new("8.0.12")) || (mariadb? && mariadb_version >= Gem::Version.new("10.3.2")))
if options[:null] == false
options = options.except(:null)
append = "
@@ -137,45 +140,53 @@
raise_error :execute, header: "Possibly dangerous operation"
when :change_column_null
table, column, null, default = args
if !null
if postgresql?
- # match https://github.com/nullobject/rein
- constraint_name = "#{table}_#{column}_null"
+ if helpers?
+ raise_error :change_column_null_postgresql_helper,
+ command: command_str(:add_null_constraint_safely, [table, column])
+ else
+ # match https://github.com/nullobject/rein
+ constraint_name = "#{table}_#{column}_null"
- raise_error :change_column_null_postgresql,
- add_constraint_code: constraint_str("ALTER TABLE %s ADD CONSTRAINT %s CHECK (%s IS NOT NULL) NOT VALID", [table, constraint_name, column]),
- validate_constraint_code: constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])
+ raise_error :change_column_null_postgresql,
+ add_constraint_code: constraint_str("ALTER TABLE %s ADD CONSTRAINT %s CHECK (%s IS NOT NULL) NOT VALID", [table, constraint_name, column]),
+ validate_constraint_code: constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])
+ end
+ elsif mysql? || mariadb?
+ raise_error :change_column_null_mysql
elsif !default.nil?
raise_error :change_column_null,
code: backfill_code(table, column, default)
end
end
when :add_foreign_key
from_table, to_table, options = args
options ||= {}
- validate = options.fetch(:validate, true)
- if postgresql?
- if ActiveRecord::VERSION::STRING >= "5.2"
- if validate
- raise_error :add_foreign_key,
- add_foreign_key_code: command_str("add_foreign_key", [from_table, to_table, options.merge(validate: false)]),
- validate_foreign_key_code: command_str("validate_foreign_key", [from_table, to_table])
- end
- else
- # always validated before 5.2
+ # always validated before 5.2
+ validate = options.fetch(:validate, true) || ActiveRecord::VERSION::STRING < "5.2"
+ if postgresql? && validate
+ if helpers?
+ raise_error :add_foreign_key_helper,
+ command: command_str(:add_foreign_key_safely, [from_table, to_table, options])
+ elsif ActiveRecord::VERSION::STRING < "5.2"
# fk name logic from rails
primary_key = options[:primary_key] || "id"
column = options[:column] || "#{to_table.to_s.singularize}_id"
hashed_identifier = Digest::SHA256.hexdigest("#{from_table}_#{column}_fk").first(10)
fk_name = options[:name] || "fk_rails_#{hashed_identifier}"
raise_error :add_foreign_key,
add_foreign_key_code: constraint_str("ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) NOT VALID", [from_table, fk_name, column, to_table, primary_key]),
validate_foreign_key_code: constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [from_table, fk_name])
+ else
+ raise_error :add_foreign_key,
+ add_foreign_key_code: command_str("add_foreign_key", [from_table, to_table, options.merge(validate: false)]),
+ validate_foreign_key_code: command_str("validate_foreign_key", [from_table, to_table])
end
end
end
StrongMigrations.checks.each do |check|
@@ -183,17 +194,55 @@
end
end
result = yield
- if StrongMigrations.auto_analyze && direction == :up && postgresql? && method == :add_index
- connection.execute "ANALYZE VERBOSE #{connection.quote_table_name(args[0].to_s)}"
+ if StrongMigrations.auto_analyze && direction == :up && method == :add_index
+ if postgresql?
+ connection.execute "ANALYZE VERBOSE #{connection.quote_table_name(args[0].to_s)}"
+ elsif mariadb? || mysql?
+ connection.execute "ANALYZE TABLE #{connection.quote_table_name(args[0].to_s)}"
+ end
end
result
end
+ def set_timeouts
+ if !@timeouts_set
+ if StrongMigrations.statement_timeout
+ statement =
+ if postgresql?
+ "SET statement_timeout TO #{connection.quote(StrongMigrations.statement_timeout)}"
+ elsif mysql?
+ "SET max_execution_time = #{connection.quote(StrongMigrations.statement_timeout.to_i * 1000)}"
+ elsif mariadb?
+ "SET max_statement_time = #{connection.quote(StrongMigrations.statement_timeout)}"
+ else
+ raise StrongMigrations::Error, "Statement timeout not supported for this database"
+ end
+
+ connection.select_all(statement)
+ end
+
+ if StrongMigrations.lock_timeout
+ statement =
+ if postgresql?
+ "SET lock_timeout TO #{connection.quote(StrongMigrations.lock_timeout)}"
+ elsif mysql? || mariadb?
+ "SET lock_wait_timeout = #{connection.quote(StrongMigrations.lock_timeout)}"
+ else
+ raise StrongMigrations::Error, "Lock timeout not supported for this database"
+ end
+
+ connection.select_all(statement)
+ end
+
+ @timeouts_set = true
+ end
+ end
+
private
def connection
@migration.connection
end
@@ -209,25 +258,59 @@
def version_safe?
version && version <= StrongMigrations.start_after
end
def postgresql?
- %w(PostgreSQL PostGIS).include?(connection.adapter_name)
+ connection.adapter_name =~ /postg/i # PostgreSQL, PostGIS
end
def postgresql_version
@postgresql_version ||= begin
- target_version = StrongMigrations.target_postgresql_version
+ target_version(StrongMigrations.target_postgresql_version) do
+ connection.select_all("SHOW server_version").first["server_version"]
+ end
+ end
+ end
+
+ def mysql?
+ connection.adapter_name =~ /mysql/i && !connection.try(:mariadb?)
+ end
+
+ def mysql_version
+ @mysql_version ||= begin
+ target_version(StrongMigrations.target_mysql_version) do
+ connection.select_all("SELECT VERSION()").first["VERSION()"].split("-").first
+ end
+ end
+ end
+
+ def mariadb?
+ connection.adapter_name =~ /mysql/i && connection.try(:mariadb?)
+ end
+
+ def mariadb_version
+ @mariadb_version ||= begin
+ target_version(StrongMigrations.target_mariadb_version) do
+ connection.select_all("SELECT VERSION()").first["VERSION()"].split("-").first
+ end
+ end
+ end
+
+ def target_version(target_version)
+ version =
if target_version && defined?(Rails) && (Rails.env.development? || Rails.env.test?)
- # we only need major version right now
- target_version.to_i * 10000
+ target_version.to_s
else
- connection.execute("SHOW server_version_num").first["server_version_num"].to_i
+ yield
end
- end
+ Gem::Version.new(version)
end
+ def helpers?
+ StrongMigrations.helpers
+ end
+
def raise_error(message_key, header: nil, **vars)
return unless StrongMigrations.check_enabled?(message_key, version: version)
message = StrongMigrations.error_messages[message_key] || "Missing message"
@@ -273,10 +356,10 @@
"#{command} #{str_args.join(", ")}"
end
def backfill_code(table, column, default)
model = table.to_s.classify
- "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.1)\n end"
+ "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
end
def new_table?(table)
@new_tables.include?(table.to_s)
end