mirror of
https://github.com/discourse/discourse.git
synced 2025-02-05 19:11:13 +00:00
6a3c8fe69c
Often we need to amend our schema, it is tempting to use drop_table, rename_column and drop_column to amned schema trouble though is that existing code that is running in production can depend on the existance of previous schema leading to application breaking until new code base is deployed. The commit enforces new rules to ensure we can never drop tables or columns in migrations and instead use Migration::ColumnDropper and Migration::TableDropper to defer drop the db objects
73 lines
2.0 KiB
Ruby
73 lines
2.0 KiB
Ruby
module Migration
|
|
class BaseDropper
|
|
def initialize(after_migration, delay, on_drop)
|
|
@after_migration = after_migration
|
|
@on_drop = on_drop
|
|
|
|
# in production we need some extra delay to allow for slow migrations
|
|
@delay = delay || (Rails.env.production? ? 3600 : 0)
|
|
end
|
|
|
|
def delayed_drop
|
|
if droppable?
|
|
@on_drop&.call
|
|
execute_drop!
|
|
|
|
Discourse.reset_active_record_cache
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def droppable?
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def execute_drop!
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def previous_migration_done
|
|
<<~SQL
|
|
EXISTS(
|
|
SELECT 1
|
|
FROM schema_migration_details
|
|
WHERE name = :after_migration AND
|
|
created_at <= (current_timestamp AT TIME ZONE 'UTC' - INTERVAL :delay)
|
|
)
|
|
SQL
|
|
end
|
|
|
|
def self.create_readonly_function(table_name, column_name = nil)
|
|
message = column_name ?
|
|
"Discourse: #{column_name} in #{table_name} is readonly" :
|
|
"Discourse: #{table_name} is read only"
|
|
|
|
ActiveRecord::Base.exec_sql <<~SQL
|
|
CREATE OR REPLACE FUNCTION #{readonly_function_name(table_name, column_name)} RETURNS trigger AS $rcr$
|
|
BEGIN
|
|
RAISE EXCEPTION '#{message}';
|
|
END
|
|
$rcr$ LANGUAGE plpgsql;
|
|
SQL
|
|
end
|
|
private_class_method :create_readonly_function
|
|
|
|
def self.validate_table_name(table_name)
|
|
raise ArgumentError.new("Invalid table name passed: #{table_name}") if table_name =~ /[^a-z0-9_]/i
|
|
end
|
|
|
|
def self.validate_column_name(column_name)
|
|
raise ArgumentError.new("Invalid column name passed to drop #{column_name}") if column_name =~ /[^a-z0-9_]/i
|
|
end
|
|
|
|
def self.readonly_function_name(table_name, column_name = nil)
|
|
["raise", table_name, column_name, "readonly()"].compact.join("_")
|
|
end
|
|
|
|
def self.readonly_trigger_name(table_name, column_name = nil)
|
|
[table_name, column_name, "readonly"].compact.join("_")
|
|
end
|
|
end
|
|
end
|