diff --git a/db/migrate/20200706193951_populate_root_account_ids_on_conversations_tables.rb b/db/migrate/20200706193951_populate_root_account_ids_on_conversations_tables.rb
new file mode 100644
index 00000000000..418e8d3301c
--- /dev/null
+++ b/db/migrate/20200706193951_populate_root_account_ids_on_conversations_tables.rb
@@ -0,0 +1,32 @@
+#
+# Copyright (C) 2020 - present Instructure, Inc.
+#
+# This file is part of Canvas.
+#
+# Canvas is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, version 3 of the License.
+#
+# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program. If not, see .
+
+class PopulateRootAccountIdsOnConversationsTables < ActiveRecord::Migration[5.2]
+ tag :postdeploy
+
+ def up
+ Conversation.find_ids_in_ranges(batch_size: 100_000) do |min, max|
+ DataFixup::PopulateRootAccountIdsOnConversationsTables.send_later_if_production_enqueue_args(
+ :run,
+ {:priority => Delayed::LOWER_PRIORITY, :n_strand => ["root_account_id_backfill_strand", Shard.current.database_server.id]},
+ min, max
+ )
+ end
+ end
+
+ def down; end
+end
diff --git a/lib/data_fixup/populate_root_account_ids_on_conversations_tables.rb b/lib/data_fixup/populate_root_account_ids_on_conversations_tables.rb
new file mode 100644
index 00000000000..d49d9f9ab97
--- /dev/null
+++ b/lib/data_fixup/populate_root_account_ids_on_conversations_tables.rb
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2020 - present Instructure, Inc.
+#
+# This file is part of Canvas.
+#
+# Canvas is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, version 3 of the License.
+#
+# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program. If not, see .
+
+module DataFixup::PopulateRootAccountIdsOnConversationsTables
+ def self.run(min, max)
+ Conversation.find_ids_in_ranges(start_at: min, end_at: max) do |batch_min, batch_max|
+ ConversationParticipant.joins(:conversation).
+ where(conversation: batch_min..batch_max).
+ update_all("root_account_ids=conversations.root_account_ids")
+
+ messages = ConversationMessage.joins(:conversation).where(conversation: batch_min..batch_max)
+ messages.update_all("root_account_ids=conversations.root_account_ids")
+
+ # only has FK to ConversationMessage and ConversationParticipant, not Conversation
+ ConversationMessageParticipant.joins(:conversation_message).
+ where(conversation_message: messages).
+ update_all("root_account_ids=conversation_messages.root_account_ids")
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/lib/data_fixup/populate_root_account_ids_on_conversations_tables_spec.rb b/spec/lib/data_fixup/populate_root_account_ids_on_conversations_tables_spec.rb
new file mode 100644
index 00000000000..ee7b6a360a3
--- /dev/null
+++ b/spec/lib/data_fixup/populate_root_account_ids_on_conversations_tables_spec.rb
@@ -0,0 +1,130 @@
+#
+# Copyright (C) 2020 - present Instructure, Inc.
+#
+# This file is part of Canvas.
+#
+# Canvas is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, version 3 of the License.
+#
+# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program. If not, see .
+
+require 'spec_helper'
+
+describe DataFixup::PopulateRootAccountIdsOnConversationsTables do
+
+ def ids_to_string(*ids)
+ ids.sort.join(',')
+ end
+
+ def reset_root_account_ids(*models)
+ models.each { |m| m.update_column(:root_account_ids, nil) }
+ end
+
+ def check_root_account_ids(expected, *models)
+ models.each { |m| expect(m.reload.root_account_ids).to eq expected}
+ end
+
+ before :once do
+ @root_account1 = account_model
+ @root_account2 = account_model
+ @user1 = user_model
+ @user2 = user_model
+ end
+
+ def check_conversation_messages(conversation, ids)
+ cm1 = ConversationMessage.create!(conversation: conversation)
+ cm2 = ConversationMessage.create!(conversation: conversation)
+ reset_root_account_ids(cm1, cm2)
+ DataFixup::PopulateRootAccountIdsOnConversationsTables.run(conversation.id, conversation.id)
+ check_root_account_ids(ids, cm1, cm2)
+ end
+
+ def check_conversation_participants(conversation, ids)
+ cp1 = ConversationParticipant.create!(conversation: conversation, user: @user1)
+ cp2 = ConversationParticipant.create!(conversation: conversation, user: @user2)
+ reset_root_account_ids(cp1, cp2)
+ DataFixup::PopulateRootAccountIdsOnConversationsTables.run(conversation.id, conversation.id)
+ check_root_account_ids(ids, cp1, cp2)
+ end
+
+ def check_conversation_message_participants(conversation, ids)
+ cm1 = ConversationMessage.create!(conversation: conversation)
+ cm2 = ConversationMessage.create!(conversation: conversation)
+ cmp1 = ConversationMessageParticipant.create!(conversation_message: cm1, user: @user1)
+ cmp2 = ConversationMessageParticipant.create!(conversation_message: cm2, user: @user2)
+ reset_root_account_ids(cm1, cm2, cmp1, cmp2)
+ DataFixup::PopulateRootAccountIdsOnConversationsTables.run(conversation.id, conversation.id)
+ check_root_account_ids(ids, cmp1, cmp2)
+ end
+
+ context 'single-account Conversation' do
+ before :once do
+ @ids = [@root_account1.id]
+ @c = Conversation.create!(root_account_ids: ids_to_string(@root_account1.id))
+ end
+
+ it 'sets root account id on all associated ConversationMessages' do
+ check_conversation_messages(@c, @ids)
+ end
+
+ it 'sets root account id on all associated ConversationParticipants' do
+ check_conversation_participants(@c, @ids)
+ end
+
+ it 'sets root account id on all ConversationMessageParticipants through ConversationMessage' do
+ check_conversation_message_participants(@c, @ids)
+ end
+ end
+
+ context 'multiple-account Conversation' do
+ before :once do
+ @ids = [@root_account1.id, @root_account2.id]
+ @c = Conversation.create!(root_account_ids: ids_to_string(@root_account1.id, @root_account2.id))
+ end
+
+ it 'sets root account ids on all associated ConversationMessages' do
+ check_conversation_messages(@c, @ids)
+ end
+
+ it 'sets root account ids on all associated ConversationParticipants' do
+ check_conversation_participants(@c, @ids)
+ end
+
+ it 'sets root account ids on all ConversationMessageParticipants through ConversationMessage' do
+ check_conversation_message_participants(@c, @ids)
+ end
+ end
+
+ context 'cross-shard Conversation' do
+ specs_require_sharding
+
+ before :once do
+ cross_shard_account = @shard1.activate do
+ account_model
+ end
+
+ @ids = [@root_account1.id, cross_shard_account.id]
+ @c = Conversation.create!(root_account_ids: ids_to_string(@root_account1.id, cross_shard_account.id))
+ end
+
+ it 'sets root account ids on all associated ConversationMessages' do
+ check_conversation_messages(@c, @ids)
+ end
+
+ it 'sets root account ids on all associated ConversationParticipants' do
+ check_conversation_participants(@c, @ids)
+ end
+
+ it 'sets root account ids on all ConversationMessageParticipants through ConversationMessage' do
+ check_conversation_message_participants(@c, @ids)
+ end
+ end
+
+end
\ No newline at end of file