From 4a6fc454293edbc9db094e663feff4761f6a4239 Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Tue, 27 Aug 2024 19:32:55 +0800 Subject: [PATCH] DEV: Migrate `User#seen_notification_id` to `bigint` (#28572) `Notification#id` was migrated to `bigint` in 799a45a291e9f2bd94278f565e58874458768079 --- app/models/user.rb | 6 +++- ...5_add_new_seen_notification_id_to_users.rb | 29 +++++++++++++++++++ ..._copy_users_seen_notification_id_values.rb | 19 ++++++++++++ ...n_id_with_seen_notification_id_on_users.rb | 26 +++++++++++++++++ lib/tasks/db.rake | 7 ++++- 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20240827063715_add_new_seen_notification_id_to_users.rb create mode 100644 db/migrate/20240827063908_copy_users_seen_notification_id_values.rb create mode 100644 db/migrate/20240827064121_swap_seen_notification_id_with_seen_notification_id_on_users.rb diff --git a/app/models/user.rb b/app/models/user.rb index f061ece5d2d..88393c76a58 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true class User < ActiveRecord::Base + self.ignored_columns = [ + :old_seen_notification_id, # TODO: Remove when column is dropped. At this point, the migration to drop the column has not been written. + ] + include Searchable include Roleable include HasCustomFields @@ -2251,7 +2255,6 @@ end # created_at :datetime not null # updated_at :datetime not null # name :string -# seen_notification_id :integer default(0), not null # last_posted_at :datetime # password_hash :string(64) # salt :string(32) @@ -2287,6 +2290,7 @@ end # last_seen_reviewable_id :integer # password_algorithm :string(64) # required_fields_version :integer +# seen_notification_id :bigint default(0), not null # # Indexes # diff --git a/db/migrate/20240827063715_add_new_seen_notification_id_to_users.rb b/db/migrate/20240827063715_add_new_seen_notification_id_to_users.rb new file mode 100644 index 00000000000..f3a73a03a51 --- /dev/null +++ b/db/migrate/20240827063715_add_new_seen_notification_id_to_users.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true +class AddNewSeenNotificationIdToUsers < ActiveRecord::Migration[7.1] + def up + # Create new column + execute "ALTER TABLE users ADD COLUMN new_seen_notification_id BIGINT NOT NULL DEFAULT(0)" + + # Mirror new `seen_notification_id` values to `new_seen_notification_id` + execute <<~SQL.squish + CREATE FUNCTION mirror_users_seen_notification_id() + RETURNS trigger AS + $$ + BEGIN + NEW.new_seen_notification_id = NEW.seen_notification_id; + RETURN NEW; + END; + $$ + LANGUAGE plpgsql + SQL + + execute <<~SQL.squish + CREATE TRIGGER users_seen_notification_id_trigger BEFORE INSERT OR UPDATE ON users + FOR EACH ROW EXECUTE PROCEDURE mirror_users_seen_notification_id() + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20240827063908_copy_users_seen_notification_id_values.rb b/db/migrate/20240827063908_copy_users_seen_notification_id_values.rb new file mode 100644 index 00000000000..5c7dc63ba7e --- /dev/null +++ b/db/migrate/20240827063908_copy_users_seen_notification_id_values.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true +class CopyUsersSeenNotificationIdValues < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + def up + min_id, max_id = execute("SELECT MIN(id), MAX(id) FROM users")[0].values + batch_size = 10_000 + + (min_id..max_id).step(batch_size) { |start_id| execute <<~SQL.squish } if min_id && max_id + UPDATE users + SET new_seen_notification_id = seen_notification_id + WHERE id >= #{start_id} AND id < #{start_id + batch_size} AND new_seen_notification_id != seen_notification_id + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20240827064121_swap_seen_notification_id_with_seen_notification_id_on_users.rb b/db/migrate/20240827064121_swap_seen_notification_id_with_seen_notification_id_on_users.rb new file mode 100644 index 00000000000..f58a7ceb3af --- /dev/null +++ b/db/migrate/20240827064121_swap_seen_notification_id_with_seen_notification_id_on_users.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +class SwapSeenNotificationIdWithSeenNotificationIdOnUsers < ActiveRecord::Migration[7.1] + def up + # Necessary to rename and drop columns + Migration::SafeMigrate.disable! + + # Drop trigger and function used to replicate new values + execute "DROP TRIGGER users_seen_notification_id_trigger ON users" + execute "DROP FUNCTION mirror_users_seen_notification_id()" + + # Swap columns + execute "ALTER TABLE users RENAME COLUMN seen_notification_id TO old_seen_notification_id" + execute "ALTER TABLE users RENAME COLUMN new_seen_notification_id TO seen_notification_id" + execute "ALTER TABLE users ALTER COLUMN old_seen_notification_id DROP NOT NULL" + execute "ALTER TABLE users ALTER COLUMN old_seen_notification_id DROP DEFAULT" + + # Keep old column and mark it as read only + Migration::ColumnDropper.mark_readonly(:users, :old_seen_notification_id) + ensure + Migration::SafeMigrate.enable! + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 4199f981d72..ecae4705b9b 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -259,7 +259,12 @@ task "db:migrate" => %w[ ActiveRecord::Tasks::DatabaseTasks.migrate SeedFu.quiet = true - SeedFu.seed(SeedHelper.paths, SeedHelper.filter) + + begin + SeedFu.seed(SeedHelper.paths, SeedHelper.filter) + rescue => error + error.backtrace.each { |l| puts l } + end Rake::Task["db:schema:cache:dump"].invoke if Rails.env.development? && !ENV["RAILS_DB"]