lib/strong_migrations/checker.rb in strong_migrations-0.6.7 vs lib/strong_migrations/checker.rb in strong_migrations-0.6.8

- old
+ new

@@ -194,16 +194,30 @@ 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" + safe = false + if postgresql_version >= Gem::Version.new("12") + # TODO likely need to quote the column in some situations + safe = constraints(table).any? { |c| c["def"] == "CHECK ((#{column} IS NOT NULL))" } + end - 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]) + unless safe + # match https://github.com/nullobject/rein + constraint_name = "#{table}_#{column}_null" + + validate_constraint_code = String.new(constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])) + if postgresql_version >= Gem::Version.new("12") + validate_constraint_code << "\n #{command_str(:change_column_null, [table, column, null])}" + validate_constraint_code << "\n #{constraint_str("ALTER TABLE %s DROP CONSTRAINT %s", [table, constraint_name])}" + end + + 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: validate_constraint_code + end elsif mysql? || mariadb? raise_error :change_column_null_mysql elsif !default.nil? raise_error :change_column_null, code: backfill_code(table, column, default) @@ -253,11 +267,10 @@ end result end - # TODO allow string timeouts in 0.7.0 def set_timeouts if !@timeouts_set if StrongMigrations.statement_timeout statement = if postgresql? @@ -377,20 +390,20 @@ end end # units: https://www.postgresql.org/docs/current/config-setting.html def timeout_to_sec(timeout) - suffixes = { + units = { "us" => 0.001, "ms" => 1, "s" => 1000, "min" => 1000 * 60, "h" => 1000 * 60 * 60, "d" => 1000 * 60 * 60 * 24 } timeout_ms = timeout.to_i - suffixes.each do |k, v| + units.each do |k, v| if timeout.end_with?(k) timeout_ms *= v break end end @@ -401,9 +414,19 @@ if timeout.is_a?(String) timeout else timeout.to_i * 1000 end + end + + def constraints(table_name) + query = <<-SQL + SELECT conname AS name, pg_get_constraintdef(oid) AS def FROM pg_constraint + WHERE contype = 'c' + AND convalidated + AND conrelid = #{connection.quote(connection.quote_table_name(table_name))}::regclass + SQL + connection.select_all(query.squish).to_a end def raise_error(message_key, header: nil, append: nil, **vars) return unless StrongMigrations.check_enabled?(message_key, version: version)