瀏覽代碼

Deduplicate accounts and make unique username/domain index case-insensitive (#7658)

Fix #6937
Fix #6837
Fix #6667
master
Eugen Rochko 6 年之前
committed by GitHub
父節點
當前提交
a16e06bbf5
沒有發現已知的金鑰在資料庫的簽署中 GPG Key ID: 4AEE18F83AFDEB23
共有 2 個文件被更改,包括 86 次插入3 次删除
  1. +84
    -0
      db/migrate/20180528141303_fix_accounts_unique_index.rb
  2. +2
    -3
      db/schema.rb

+ 84
- 0
db/migrate/20180528141303_fix_accounts_unique_index.rb 查看文件

@@ -0,0 +1,84 @@
class FixAccountsUniqueIndex < ActiveRecord::Migration[5.2]
disable_ddl_transaction!

def up
say ''
say 'WARNING: This migration may take a *long* time for large instances'
say 'It will *not* lock tables for any significant time, but it may run'
say 'for a very long time. We will pause for 10 seconds to allow you to'
say 'interrupt this migration if you are not ready.'
say ''
say 'This migration will irreversibly delete user accounts with duplicate'
say 'usernames. You may use the `rake mastodon:maintenance:find_duplicate_usernames`'
say 'task to manually deal with such accounts before running this migration.'

10.downto(1) do |i|
say "Continuing in #{i} second#{i == 1 ? '' : 's'}...", true
sleep 1
end

duplicates = Account.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM accounts GROUP BY lower(username), lower(domain) HAVING count(*) > 1').to_hash

duplicates.each do |row|
deduplicate_account!(row['ids'].split(','))
end

remove_index :accounts, name: 'index_accounts_on_username_and_domain_lower' if index_name_exists?(:accounts, 'index_accounts_on_username_and_domain_lower')
safety_assured { execute 'CREATE UNIQUE INDEX CONCURRENTLY index_accounts_on_username_and_domain_lower ON accounts (lower(username), lower(domain))' }
remove_index :accounts, name: 'index_accounts_on_username_and_domain' if index_name_exists?(:accounts, 'index_accounts_on_username_and_domain')
end

def down
raise ActiveRecord::IrreversibleMigration
end

private

def deduplicate_account!(account_ids)
accounts = Account.where(id: account_ids).to_a
accounts = account.first.local? ? accounts.sort_by(&:created_at) : accounts.sort_by(&:updated_at).reverse
reference_account = accounts.shift

accounts.each do |other_account|
if other_account.public_key == reference_account.public_key
# The accounts definitely point to the same resource, so
# it's safe to re-attribute content and relationships
merge_accounts!(reference_account, other_account)
elsif other_account.local?
# Since domain is in the GROUP BY clause, both accounts
# are always either going to be local or not local, so only
# one check is needed. Since we cannot support two users with
# the same username locally, one has to go. 😢
other_account.user.destroy
end

other_account.destroy
end
end

def merge_accounts!(main_account, duplicate_account)
[Status, Favourite, Mention, StatusPin, StreamEntry].each do |klass|
klass.where(account_id: duplicate_account.id).update_all(account_id: main_account.id)
end

# Since it's the same remote resource, the remote resource likely
# already believes we are following/blocking, so it's safe to
# re-attribute the relationships too. However, during the presence
# of the index bug users could have *also* followed the reference
# account already, therefore mass update will not work and we need
# to check for (and skip past) uniqueness errors
[Follow, FollowRequest, Block, Mute].each do |klass|
klass.where(account_id: duplicate_account.id).find_each do |record|
record.update(account_id: main_account.id)
rescue ActiveRecord::RecordNotUnique
next
end

klass.where(target_account_id: duplicate_account.id).find_each do |record|
record.update(target_account_id: main_account.id)
rescue ActiveRecord::RecordNotUnique
next
end
end
end
end

+ 2
- 3
db/schema.rb 查看文件

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2018_05_14_140000) do
ActiveRecord::Schema.define(version: 2018_05_28_141303) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -77,10 +77,9 @@ ActiveRecord::Schema.define(version: 2018_05_14_140000) do
t.jsonb "fields"
t.string "actor_type"
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower"
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
t.index ["uri"], name: "index_accounts_on_uri"
t.index ["url"], name: "index_accounts_on_url"
t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true
end

create_table "admin_action_logs", force: :cascade do |t|


Loading…
取消
儲存