class RemoveRedundantPipelineStages < ActiveRecord::Migration[4.2]
  include Gitlab::Database::MigrationHelpers

  DOWNTIME = false

  disable_ddl_transaction!

  def up(attempts: 100)
    remove_redundant_pipeline_stages!
    remove_outdated_index!
    add_unique_index!
  rescue ActiveRecord::RecordNotUnique
    retry if (attempts -= 1) > 0

    raise StandardError, <<~EOS
      Failed to add an unique index to ci_stages, despite retrying the
      migration 100 times.

      See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16580.
    EOS
  end

  def down
    remove_concurrent_index :ci_stages, [:pipeline_id, :name], unique: true
    add_concurrent_index :ci_stages, [:pipeline_id, :name]
  end

  private

  def remove_outdated_index!
    return unless index_exists?(:ci_stages, [:pipeline_id, :name])

    remove_concurrent_index :ci_stages, [:pipeline_id, :name]
  end

  def add_unique_index!
    add_concurrent_index :ci_stages, [:pipeline_id, :name], unique: true
  end

  def remove_redundant_pipeline_stages!
    disable_statement_timeout do
      redundant_stages_ids = <<~SQL
        SELECT id FROM ci_stages WHERE (pipeline_id, name) IN (
          SELECT pipeline_id, name FROM ci_stages
            GROUP BY pipeline_id, name HAVING COUNT(*) > 1
        )
      SQL

      execute <<~SQL
        UPDATE ci_builds SET stage_id = NULL WHERE stage_id IN (#{redundant_stages_ids})
      SQL

      if Gitlab::Database.postgresql?
        execute <<~SQL
          DELETE FROM ci_stages WHERE id IN (#{redundant_stages_ids})
        SQL
      else # We can't modify a table we are selecting from on MySQL
        execute <<~SQL
          DELETE a FROM ci_stages AS a, ci_stages AS b
            WHERE a.pipeline_id = b.pipeline_id AND a.name = b.name
              AND a.id <> b.id
        SQL
      end
    end
  end
end
