discourse/lib/db_helper.rb
Osama Sayegh 45ccadeeeb
DEV: Upgrade Rails to 6.1.3.1 (#12688)
Rails 6.1.3.1 deprecates a few API and has some internal changes that break our tests suite, so this commit fixes all the deprecations and errors and now Discourse should be fully compatible with Rails 6.1.3.1. We also have a new release of the rails_failover gem that's compatible with Rails 6.1.3.1.
2021-04-21 12:36:32 +03:00

137 lines
3.8 KiB
Ruby

# frozen_string_literal: true
require_dependency "migration/base_dropper"
class DbHelper
REMAP_SQL ||= <<~SQL
SELECT table_name::text, column_name::text, character_maximum_length
FROM information_schema.columns
WHERE table_schema = 'public'
AND is_updatable = 'YES'
AND (data_type LIKE 'char%' OR data_type LIKE 'text%')
ORDER BY table_name, column_name
SQL
TRIGGERS_SQL ||= <<~SQL
SELECT trigger_name::text
FROM information_schema.triggers
WHERE trigger_name LIKE '%_readonly'
SQL
TRUNCATABLE_COLUMNS ||= [
'topic_links.url'
]
def self.remap(from, to, anchor_left: false, anchor_right: false, excluded_tables: [], verbose: false)
like = "#{anchor_left ? '' : "%"}#{from}#{anchor_right ? '' : "%"}"
text_columns = find_text_columns(excluded_tables)
text_columns.each do |table, columns|
set = columns.map do |column|
replace = "REPLACE(\"#{column[:name]}\", :from, :to)"
replace = truncate(replace, table, column)
"\"#{column[:name]}\" = #{replace}"
end.join(", ")
where = columns.map do |column|
"\"#{column[:name]}\" IS NOT NULL AND \"#{column[:name]}\" LIKE :like"
end.join(" OR ")
rows = DB.exec(<<~SQL, from: from, to: to, like: like)
UPDATE \"#{table}\"
SET #{set}
WHERE #{where}
SQL
puts "#{table}=#{rows}" if verbose && rows > 0
end
finish!
end
def self.regexp_replace(pattern, replacement, flags: "gi", match: "~*", excluded_tables: [], verbose: false)
text_columns = find_text_columns(excluded_tables)
text_columns.each do |table, columns|
set = columns.map do |column|
replace = "REGEXP_REPLACE(\"#{column[:name]}\", :pattern, :replacement, :flags)"
replace = truncate(replace, table, column)
"\"#{column[:name]}\" = #{replace}"
end.join(", ")
where = columns.map do |column|
"\"#{column[:name]}\" IS NOT NULL AND \"#{column[:name]}\" #{match} :pattern"
end.join(" OR ")
rows = DB.exec(<<~SQL, pattern: pattern, replacement: replacement, flags: flags, match: match)
UPDATE \"#{table}\"
SET #{set}
WHERE #{where}
SQL
puts "#{table}=#{rows}" if verbose && rows > 0
end
finish!
end
def self.find(needle, anchor_left: false, anchor_right: false, excluded_tables: [])
found = {}
like = "#{anchor_left ? '' : "%"}#{needle}#{anchor_right ? '' : "%"}"
DB.query(REMAP_SQL).each do |r|
next if excluded_tables.include?(r.table_name)
rows = DB.query(<<~SQL, like: like)
SELECT \"#{r.column_name}\"
FROM \"#{r.table_name}\"
WHERE \""#{r.column_name}"\" LIKE :like
SQL
if rows.size > 0
found["#{r.table_name}.#{r.column_name}"] = rows.map do |row|
row.public_send(r.column_name)
end
end
end
found
end
private
def self.finish!
SiteSetting.refresh!
Theme.expire_site_cache!
SiteIconManager.ensure_optimized!
ApplicationController.banner_json_cache.clear
end
def self.find_text_columns(excluded_tables)
triggers = DB.query(TRIGGERS_SQL).map(&:trigger_name).to_set
text_columns = Hash.new { |h, k| h[k] = [] }
DB.query(REMAP_SQL).each do |r|
next if excluded_tables.include?(r.table_name) ||
triggers.include?(Migration::BaseDropper.readonly_trigger_name(r.table_name, r.column_name)) ||
triggers.include?(Migration::BaseDropper.readonly_trigger_name(r.table_name))
text_columns[r.table_name] << {
name: r.column_name,
max_length: r.character_maximum_length
}
end
text_columns
end
def self.truncate(sql, table, column)
if column[:max_length] && TRUNCATABLE_COLUMNS.include?("#{table}.#{column[:name]}")
"LEFT(#{sql}, #{column[:max_length]})"
else
sql
end
end
end