discourse/lib/migration/base_dropper.rb
Sam 5f64fd0a21 DEV: remove exec_sql and replace with mini_sql
Introduce new patterns for direct sql that are safe and fast.

MiniSql is not prone to memory bloat that can happen with direct PG usage.
It also has an extremely fast materializer and very a convenient API

- DB.exec(sql, *params) => runs sql returns row count
- DB.query(sql, *params) => runs sql returns usable objects (not a hash)
- DB.query_hash(sql, *params) => runs sql returns an array of hashes
- DB.query_single(sql, *params) => runs sql and returns a flat one dimensional array
- DB.build(sql) => returns a sql builder

See more at: https://github.com/discourse/mini_sql
2018-06-19 16:13:36 +10:00

78 lines
2.2 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) OR
(SELECT created_at
FROM schema_migration_details
ORDER BY id ASC
LIMIT 1) > (current_timestamp AT TIME ZONE 'UTC' - INTERVAL '10 minutes')
)
)
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"
DB.exec <<~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