# frozen_string_literal: true class MigrateEmailToNormalizedEmail < ActiveRecord::Migration[6.1] # minimize locking on user_email table disable_ddl_transaction! def up min, max = DB.query_single "SELECT MIN(id), MAX(id) FROM user_emails" # scaling is needed to compensate for "holes" where records were deleted # and pathological cases where for some reason id 100_000_000 and 0 exist # avoid doing any work on empty dbs return if min.nil? bounds = DB.query_single <<~SQL SELECT t.id FROM ( SELECT *, row_number() OVER(ORDER BY id ASC) AS row FROM user_emails ) t WHERE t.row % 100000 = 0 SQL # subtle but loop does < not <= # includes low, excludes high bounds << (max + 1) low_id = min bounds.each do |high_id| # using execute cause MiniSQL is not logging at the moment # to_i is not needed, but specified so it is explicit there is no SQL injection execute <<~SQL UPDATE user_emails SET normalized_email = REPLACE(REGEXP_REPLACE(email,'([+@].*)',''),'.','') || REGEXP_REPLACE(email, '[^@]*', '') WHERE (normalized_email IS NULL OR normalized_email <> (REPLACE(REGEXP_REPLACE(email,'([+@].*)',''),'.','') || REGEXP_REPLACE(email, '[^@]*', ''))) AND (id >= #{low_id.to_i} AND id < #{high_id.to_i}) SQL low_id = high_id end end def down execute "UPDATE user_emails SET normalized_email = null" end end