discourse/script/db_timestamps_mover.rb
Andrei Prigorshnev 696bd0bf05
DEV: add a script for moving timestamps in database (#13682)
We're going to use this script for updating timestamps on Try, but it can be used with a local database during development as well.

Usage:

Commands:
  ruby db_timestamp_updater.rb yesterday <date> move all timestamps by x days so that <date> will be moved to yesterday
  ruby db_timestamp_updater.rb 100              move all timestamps forward by 100 days
  ruby db_timestamp_updater.rb -100             move all timestamps backward by 100 days
The script moves all timestamps in the database by the same amount of days forward or backward. No need to change the script if we add a new column in the future.

The more simple solution would be just to move timestamps in several tables (topics, posts, and so on). I didn't want to go that way because it could generate additional work in the future. For example, if we add a new column with a timestamp and users can see that timestamp we'd need to add that column to the script. Or, for example, if we move a post's timestamp to the future but forget to move a timestamp of topic timer or user action it can cause weird bugs.
2021-07-09 20:04:28 +04:00

88 lines
2.3 KiB
Ruby

# frozen_string_literal: true
require "pg"
usage = <<-END
Commands:
ruby db_timestamp_updater.rb yesterday <date> move all timestamps by x days so that <date> will be moved to yesterday
ruby db_timestamp_updater.rb 100 move all timestamps forward by 100 days
ruby db_timestamp_updater.rb -100 move all timestamps backward by 100 days
END
class TimestampsUpdater
TABLE_SCHEMA = 'public'
def initialize
@raw_connection = PG.connect(
host: ENV['DISCOURSE_DB_HOST'] || 'localhost',
port: ENV['DISCOURSE_DB_PORT'] || 5432,
dbname: ENV['DISCOURSE_DB_NAME'] || 'discourse_development',
user: ENV['DISCOURSE_DB_USERNAME'] || 'postgres',
password: ENV['DISCOURSE_DB_PASSWORD'] || '')
end
def move_by(days)
postgresql_date_types = [
"timestamp without time zone",
"timestamp with time zone",
"date"
]
postgresql_date_types.each do |data_type|
columns = all_columns_of_type(data_type)
columns.each do |c|
table = c["table_name"]
column = c["column_name"]
move_timestamps table, column, days
end
end
end
def move_to_yesterday(date)
days = (Date.today.prev_day - date).to_i
move_by days
end
private
def all_columns_of_type(data_type)
sql = <<~SQL
SELECT c.column_name, c.table_name
FROM information_schema.columns AS c
JOIN information_schema.tables AS t
ON c.table_name = t.table_name
WHERE c.table_schema = '#{TABLE_SCHEMA}'
AND c.data_type = '#{data_type}'
AND t.table_type = 'BASE TABLE'
SQL
@raw_connection.exec(sql)
end
def move_timestamps(table_name, column_name, days)
operator = days < 0 ? "-" : "+"
sql = <<~SQL
UPDATE #{table_name}
SET #{column_name} = #{column_name} #{operator} INTERVAL '#{days.abs} day'
SQL
@raw_connection.exec(sql)
end
end
def is_i?(string)
true if Integer(string) rescue false
end
def is_date?(string)
true if Date.parse(string) rescue false
end
if ARGV.length == 2 && ARGV[0] == "yesterday" && is_date?(ARGV[1])
date = Date.parse(ARGV[1])
TimestampsUpdater.new.move_to_yesterday date
elsif ARGV.length == 1 && is_i?(ARGV[0])
days = ARGV[0].to_i
TimestampsUpdater.new.move_by days
else
puts usage
exit 1
end