2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-03-20 15:20:50 +08:00
|
|
|
module Migration
|
|
|
|
end
|
|
|
|
|
|
|
|
class Discourse::InvalidMigration < StandardError
|
|
|
|
end
|
|
|
|
|
|
|
|
class Migration::SafeMigrate
|
|
|
|
module SafeMigration
|
|
|
|
@@enable_safe = true
|
|
|
|
|
|
|
|
def self.enable_safe!
|
|
|
|
@@enable_safe = true
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.disable_safe!
|
|
|
|
@@enable_safe = false
|
|
|
|
end
|
|
|
|
|
|
|
|
def migrate(direction)
|
2018-10-08 15:47:38 +08:00
|
|
|
if direction == :up && version &&
|
2021-06-22 23:02:24 +08:00
|
|
|
version > Migration::SafeMigrate.earliest_post_deploy_version &&
|
2018-10-08 15:47:38 +08:00
|
|
|
@@enable_safe != false && !is_post_deploy_migration?
|
2018-03-20 15:20:50 +08:00
|
|
|
Migration::SafeMigrate.enable!
|
|
|
|
end
|
2018-10-08 15:47:38 +08:00
|
|
|
|
2018-03-20 15:20:50 +08:00
|
|
|
super
|
|
|
|
ensure
|
|
|
|
Migration::SafeMigrate.disable!
|
|
|
|
end
|
2018-10-08 15:47:38 +08:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def is_post_deploy_migration?
|
2020-05-15 14:23:27 +08:00
|
|
|
instance_methods = self.class.instance_methods(false)
|
|
|
|
|
2018-10-08 15:47:38 +08:00
|
|
|
method =
|
2020-05-15 14:23:27 +08:00
|
|
|
if instance_methods.include?(:up)
|
2018-10-08 15:47:38 +08:00
|
|
|
:up
|
2020-05-15 14:23:27 +08:00
|
|
|
elsif instance_methods.include?(:change)
|
2018-10-08 15:47:38 +08:00
|
|
|
:change
|
|
|
|
end
|
|
|
|
|
2020-05-15 14:23:27 +08:00
|
|
|
return false if !method
|
|
|
|
|
2018-10-08 15:47:38 +08:00
|
|
|
self.method(method).source_location.first.include?(Discourse::DB_POST_MIGRATE_PATH)
|
|
|
|
end
|
2018-03-20 15:20:50 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
module NiceErrors
|
|
|
|
def migrate
|
|
|
|
super
|
|
|
|
rescue => e
|
|
|
|
if e.cause.is_a?(Discourse::InvalidMigration)
|
2018-12-04 11:48:13 +08:00
|
|
|
def e.cause
|
|
|
|
nil
|
|
|
|
end
|
2018-03-20 15:20:50 +08:00
|
|
|
def e.backtrace
|
|
|
|
super.reject do |frame|
|
|
|
|
frame =~ /safe_migrate\.rb/ || frame =~ /schema_migration_details\.rb/
|
|
|
|
end
|
|
|
|
end
|
|
|
|
raise e
|
|
|
|
else
|
|
|
|
raise e
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-01-13 07:12:27 +08:00
|
|
|
def self.post_migration_path
|
|
|
|
Discourse::DB_POST_MIGRATE_PATH
|
|
|
|
end
|
|
|
|
|
2018-03-20 15:20:50 +08:00
|
|
|
def self.enable!
|
|
|
|
return if PG::Connection.method_defined?(:exec_migrator_unpatched)
|
2020-04-14 09:21:31 +08:00
|
|
|
return if ENV["RAILS_ENV"] == "production"
|
2018-03-20 15:20:50 +08:00
|
|
|
|
|
|
|
PG::Connection.class_eval do
|
|
|
|
alias_method :exec_migrator_unpatched, :exec
|
|
|
|
alias_method :async_exec_migrator_unpatched, :async_exec
|
|
|
|
|
|
|
|
def exec(*args, &blk)
|
|
|
|
Migration::SafeMigrate.protect!(args[0])
|
|
|
|
exec_migrator_unpatched(*args, &blk)
|
|
|
|
end
|
|
|
|
|
|
|
|
def async_exec(*args, &blk)
|
|
|
|
Migration::SafeMigrate.protect!(args[0])
|
|
|
|
async_exec_migrator_unpatched(*args, &blk)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.disable!
|
|
|
|
return if !PG::Connection.method_defined?(:exec_migrator_unpatched)
|
2020-04-14 09:21:31 +08:00
|
|
|
return if ENV["RAILS_ENV"] == "production"
|
|
|
|
|
2018-03-20 15:20:50 +08:00
|
|
|
PG::Connection.class_eval do
|
|
|
|
alias_method :exec, :exec_migrator_unpatched
|
|
|
|
alias_method :async_exec, :async_exec_migrator_unpatched
|
|
|
|
|
|
|
|
remove_method :exec_migrator_unpatched
|
|
|
|
remove_method :async_exec_migrator_unpatched
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.patch_active_record!
|
2020-04-14 09:21:31 +08:00
|
|
|
return if ENV["RAILS_ENV"] == "production"
|
|
|
|
|
2018-03-20 15:20:50 +08:00
|
|
|
ActiveSupport.on_load(:active_record) { ActiveRecord::Migration.prepend(SafeMigration) }
|
|
|
|
|
|
|
|
if defined?(ActiveRecord::Tasks::DatabaseTasks)
|
|
|
|
ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(NiceErrors)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.protect!(sql)
|
2023-01-21 02:52:49 +08:00
|
|
|
if sql =~ /\A\s*(?:drop\s+table|alter\s+table.*rename\s+to)\s+/i
|
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
|
|
|
$stdout.puts("", <<~TEXT)
|
2018-03-20 15:20:50 +08:00
|
|
|
WARNING
|
|
|
|
-------------------------------------------------------------------------------------
|
2018-03-22 00:52:05 +08:00
|
|
|
An attempt was made to drop or rename a table in a migration
|
2018-03-20 15:20:50 +08:00
|
|
|
SQL used was: '#{sql}'
|
2018-10-08 15:47:38 +08:00
|
|
|
Please generate a post deployment migration using `rails g post_migration` to drop
|
2018-03-22 00:52:05 +08:00
|
|
|
or rename the table.
|
2018-03-20 15:20:50 +08:00
|
|
|
|
|
|
|
This protection is in place to protect us against dropping tables that are currently
|
|
|
|
in use by live applications.
|
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
|
|
|
TEXT
|
2018-03-20 15:20:50 +08:00
|
|
|
raise Discourse::InvalidMigration, "Attempt was made to drop a table"
|
2023-03-22 22:43:32 +08:00
|
|
|
elsif sql =~ /\A\s*alter\s+table.*(?:rename|drop(?!\s+not\s+null))\s+/i
|
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
|
|
|
$stdout.puts("", <<~TEXT)
|
2018-03-20 15:20:50 +08:00
|
|
|
WARNING
|
|
|
|
-------------------------------------------------------------------------------------
|
|
|
|
An attempt was made to drop or rename a column in a migration
|
|
|
|
SQL used was: '#{sql}'
|
2018-10-08 15:47:38 +08:00
|
|
|
|
|
|
|
Please generate a post deployment migration using `rails g post_migration` to drop
|
2018-03-20 15:20:50 +08:00
|
|
|
or rename columns.
|
|
|
|
|
|
|
|
Note, to minimize disruption use self.ignored_columns = ["column name"] on your
|
|
|
|
ActiveRecord model, this can be removed 6 months or so later.
|
|
|
|
|
|
|
|
This protection is in place to protect us against dropping columns that are currently
|
|
|
|
in use by live applications.
|
DEV: Correctly tag heredocs (#16061)
This allows text editors to use correct syntax coloring for the heredoc sections.
Heredoc tag names we use:
languages: SQL, JS, RUBY, LUA, HTML, CSS, SCSS, SH, HBS, XML, YAML/YML, MF, ICS
other: MD, TEXT/TXT, RAW, EMAIL
2022-03-01 03:50:55 +08:00
|
|
|
TEXT
|
2018-03-20 15:20:50 +08:00
|
|
|
raise Discourse::InvalidMigration, "Attempt was made to rename or delete column"
|
|
|
|
end
|
|
|
|
end
|
2021-06-22 23:02:24 +08:00
|
|
|
|
|
|
|
def self.earliest_post_deploy_version
|
|
|
|
@@earliest_post_deploy_version ||=
|
|
|
|
begin
|
|
|
|
first_file = Dir.glob("#{Discourse::DB_POST_MIGRATE_PATH}/*.rb").sort.first
|
|
|
|
file_name = File.basename(first_file, ".rb")
|
|
|
|
file_name.first(14).to_i
|
|
|
|
end
|
|
|
|
end
|
2018-03-20 15:20:50 +08:00
|
|
|
end
|