2018-03-20 15:20:50 +08:00
|
|
|
module Migration
|
|
|
|
class BaseDropper
|
2018-08-23 12:51:22 +08:00
|
|
|
FUNCTION_SCHEMA_NAME = "discourse_functions".freeze
|
|
|
|
|
2018-07-09 16:54:18 +08:00
|
|
|
def initialize(after_migration, delay, on_drop, after_drop)
|
2018-03-20 15:20:50 +08:00
|
|
|
@after_migration = after_migration
|
|
|
|
@on_drop = on_drop
|
2018-07-09 16:54:18 +08:00
|
|
|
@after_drop = after_drop
|
2018-03-20 15:20:50 +08:00
|
|
|
|
|
|
|
# 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!
|
2018-07-09 16:54:18 +08:00
|
|
|
@after_drop&.call
|
2018-03-20 15:20:50 +08:00
|
|
|
|
|
|
|
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
|
2018-03-26 22:51:27 +08:00
|
|
|
(created_at <= (current_timestamp AT TIME ZONE 'UTC' - INTERVAL :delay) OR
|
|
|
|
(SELECT created_at
|
|
|
|
FROM schema_migration_details
|
|
|
|
ORDER BY id ASC
|
|
|
|
LIMIT 1) > (current_timestamp AT TIME ZONE 'UTC' - INTERVAL '10 minutes')
|
|
|
|
)
|
2018-03-20 15:20:50 +08:00
|
|
|
)
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.create_readonly_function(table_name, column_name = nil)
|
2018-08-23 12:51:22 +08:00
|
|
|
DB.exec <<~SQL
|
|
|
|
CREATE SCHEMA IF NOT EXISTS #{FUNCTION_SCHEMA_NAME};
|
|
|
|
SQL
|
|
|
|
|
2018-03-20 15:20:50 +08:00
|
|
|
message = column_name ?
|
|
|
|
"Discourse: #{column_name} in #{table_name} is readonly" :
|
|
|
|
"Discourse: #{table_name} is read only"
|
|
|
|
|
2018-06-19 14:13:14 +08:00
|
|
|
DB.exec <<~SQL
|
2018-03-20 15:20:50 +08:00
|
|
|
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)
|
2018-08-23 12:51:22 +08:00
|
|
|
function_name = [
|
|
|
|
"raise",
|
|
|
|
table_name,
|
|
|
|
column_name,
|
|
|
|
"readonly()"
|
|
|
|
].compact.join("_")
|
|
|
|
|
|
|
|
"#{FUNCTION_SCHEMA_NAME}.#{function_name}"
|
2018-03-20 15:20:50 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.readonly_trigger_name(table_name, column_name = nil)
|
|
|
|
[table_name, column_name, "readonly"].compact.join("_")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|