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