diff --git a/db/migrate/20200601130900_correct_schema_discrepancies.rb b/db/migrate/20200601130900_correct_schema_discrepancies.rb new file mode 100644 index 0000000000..dc7e1bdd9c --- /dev/null +++ b/db/migrate/20200601130900_correct_schema_discrepancies.rb @@ -0,0 +1,249 @@ +# frozen_string_literal: true + +class CorrectSchemaDiscrepancies < ActiveRecord::Migration[6.0] + disable_ddl_transaction! + + def up + timestamp_columns = %w{ + category_tag_groups.created_at + category_tag_groups.updated_at + category_tags.created_at + category_tags.updated_at + child_themes.created_at + child_themes.updated_at + embeddable_hosts.created_at + embeddable_hosts.updated_at + group_archived_messages.created_at + group_archived_messages.updated_at + group_mentions.created_at + group_mentions.updated_at + muted_users.created_at + muted_users.updated_at + permalinks.created_at + permalinks.updated_at + post_stats.created_at + post_stats.updated_at + remote_themes.created_at + remote_themes.updated_at + stylesheet_cache.created_at + stylesheet_cache.updated_at + tag_group_memberships.created_at + tag_group_memberships.updated_at + tag_groups.created_at + tag_groups.updated_at + tag_users.created_at + tag_users.updated_at + tags.created_at + tags.updated_at + theme_fields.created_at + theme_fields.updated_at + topic_tags.created_at + topic_tags.updated_at + topic_timers.created_at + topic_timers.updated_at + unsubscribe_keys.created_at + unsubscribe_keys.updated_at + user_api_keys.created_at + user_api_keys.updated_at + user_archived_messages.created_at + user_archived_messages.updated_at + user_auth_tokens.created_at + user_auth_tokens.updated_at + user_emails.created_at + user_emails.updated_at + user_exports.created_at + user_exports.updated_at + user_field_options.created_at + user_field_options.updated_at + user_fields.created_at + user_fields.updated_at + user_warnings.created_at + user_warnings.updated_at + watched_words.created_at + watched_words.updated_at + web_hook_events.created_at + web_hook_events.updated_at + web_hooks.created_at + web_hooks.updated_at + } + + char_limit_columns = %w{ + badge_groupings.name + badge_posts.action_code + badge_posts.edit_reason + badge_types.name + badges.icon + badges.name + categories.email_in + categories.slug + color_scheme_colors.hex + color_scheme_colors.name + color_schemes.name + draft_sequences.draft_key + drafts.draft_key + email_logs.email_type + email_logs.to_address + email_tokens.email + email_tokens.token + embeddable_hosts.host + github_user_infos.screen_name + groups.name + groups.title + invites.email + message_bus.context + message_bus.name + oauth2_user_infos.email + oauth2_user_infos.name + oauth2_user_infos.provider + oauth2_user_infos.uid + optimized_images.url + plugin_store_rows.key + plugin_store_rows.plugin_name + plugin_store_rows.type_name + post_details.key + post_details.value + post_search_data.locale + posts.action_code + posts.edit_reason + schema_migrations.version + screened_emails.email + screened_urls.domain + screened_urls.url + single_sign_on_records.external_email + single_sign_on_records.external_id + single_sign_on_records.external_name + single_sign_on_records.external_username + site_settings.name + stylesheet_cache.digest + stylesheet_cache.target + themes.name + topic_links.title + topic_search_data.locale + topics.archetype + topics.slug + topics.subtype + topics.title + uploads.original_filename + uploads.url + user_exports.file_name + user_field_options.value + user_fields.description + user_fields.field_type + user_fields.name + user_histories.context + user_histories.custom_type + user_histories.email + user_histories.ip_address + user_open_ids.email + user_open_ids.url + user_profiles.location + user_profiles.website + users.name + users.title + } + + float_default_columns = %w{ + top_topics.all_score + top_topics.daily_score + top_topics.monthly_score + top_topics.weekly_score + top_topics.yearly_score + } + + other_default_columns = %w{ + categories.color + topic_search_data.topic_id + } + + lookup_sql = ( + timestamp_columns + + char_limit_columns + + float_default_columns + + other_default_columns + ).map do |ref| + table, column = ref.split(".") + "(table_name='#{table}' AND column_name='#{column}')" + end.join(" OR ") + + raw_info = DB.query_hash <<~SQL + SELECT table_name, column_name, is_nullable, character_maximum_length, column_default + FROM information_schema.columns + WHERE table_schema='public' + AND ( + #{lookup_sql} + ) + SQL + + schema_hash = {} + + raw_info.each do |row| + schema_hash["#{row["table_name"]}.#{row["column_name"]}"] = row + end + + # In the past, rails changed the default behavior for timestamp columns + # This only affects older discourse installations + # This migration will make old database schemas match modern behavior + timestamp_columns.each do |ref| + current_value = schema_hash[ref]["is_nullable"] + next if current_value == "NO" + + table, column = ref.split(".") + + # There shouldn't be any null values - rails inserts timestamps automatically + # But just in case, set them to now() if there are any nulls + DB.exec <<~SQL + UPDATE #{table} SET #{column} = now() WHERE #{column} IS NULL + SQL + + DB.exec <<~SQL + ALTER TABLE #{table} ALTER COLUMN #{column} SET NOT NULL + SQL + end + + # In the past, rails changed the default behavior for varchar columns + # This only affects older discourse installations + # This migration removes the character limits from columns, so that they match modern behavior + char_limit_columns.each do |ref| + current_value = schema_hash[ref]["character_maximum_length"] + next if current_value == nil + table, column = ref.split(".") + + DB.exec <<~SQL + ALTER TABLE #{table} ALTER COLUMN #{column} TYPE varchar + SQL + end + + # In the past, rails changed the default behavior for float columns + # This only affects older discourse installations + # This migration updates the default values, so that they match modern behavior + float_default_columns.each do |ref| + current_value = schema_hash[ref]["column_default"] + next if current_value == "0.0" + table, column = ref.split(".") + + DB.exec <<~SQL + ALTER TABLE #{table} ALTER COLUMN #{column} SET DEFAULT 0.0 + SQL + end + + # Category color default was changed in https://github.com/discourse/discourse/commit/faf09bb8c80fcb28b132a5a644ac689cc9abffc2 + # But should have been added in a new migration + if schema_hash["categories.color"]["column_default"] != "'0088CC'::character varying" + DB.exec <<~SQL + ALTER TABLE categories ALTER COLUMN color SET DEFAULT '0088CC' + SQL + end + + # Older sites have a default value like nextval('topic_search_data_topic_id_seq'::regclass) + # Modern sites do not. This is likely caused by another historical change in rails + if schema_hash["topic_search_data.topic_id"]["column_default"] != nil + DB.exec <<~SQL + ALTER TABLE topic_search_data ALTER COLUMN topic_id SET DEFAULT NULL + SQL + end + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end