defmodule Ecto.Integration.AlterTest do
  use Ecto.Integration.Case, async: false

  alias Ecto.Integration.PoolRepo

  defmodule AlterMigrationOne do
    use Ecto.Migration

    def up do
      create table(:alter_col_type) do
        add :value, :integer
      end

      execute "INSERT INTO alter_col_type (value) VALUES (1)"
    end

    def down do
      drop table(:alter_col_type)
    end
  end

  defmodule AlterMigrationTwo do
    use Ecto.Migration

    def up do
      alter table(:alter_col_type) do
        modify :value, :numeric
      end
    end

    def down do
      alter table(:alter_col_type) do
        modify :value, :integer
      end
    end
  end

  import Ecto.Query, only: [from: 1, from: 2]
  import Ecto.Migrator, only: [up: 4, down: 4]

  test "reset cache on returning query after alter column type" do
    values = from v in "alter_col_type", select: v.value

    assert :ok == up(PoolRepo, 20161112120000, AlterMigrationOne, log: false)
    assert PoolRepo.all(values) == [1]

    assert :ok == up(PoolRepo, 20161112130000, AlterMigrationTwo, log: false)

    # optionally fail once with ArgumentError when preparing query prepared on
    # another connection (and clear cache)
    try do
      PoolRepo.all(values)
    rescue
      err in [ArgumentError] ->
        assert Exception.message(err) =~ "stale type"
        assert [%Decimal{}] = PoolRepo.all(values)
    else
      result ->
        assert [%Decimal{}] = result
    end

    PoolRepo.transaction(fn() ->
      assert [%Decimal{}] = PoolRepo.all(values)

      assert :ok == down(PoolRepo, 20161112130000, AlterMigrationTwo, log: false)

      # optionally fail once with database error when already prepared on
      # connection (and clear cache)
      try do
        PoolRepo.all(values, [mode: :savepoint])
      catch
        :error, _ ->
          assert PoolRepo.all(values) == [1]
      else
        result ->
          assert result == [1]
      end
    end)

  after
    assert :ok == down(PoolRepo, 20161112120000, AlterMigrationOne, log: false)
  end

  test "reset cache on paramterised query after alter column type" do
    values = from v in "alter_col_type"

    assert :ok == up(PoolRepo, 20161112120000, AlterMigrationOne, log: false)
    assert PoolRepo.update_all(values, [set: [value: 2]]) == {1, []}

    assert :ok == up(PoolRepo, 20161112130000, AlterMigrationTwo, log: false)

    # optionally fail once with ArgumentError when preparing query prepared on
    # another connection (and clear cache)
    try do
      PoolRepo.update_all(values, [set: [value: 3]])
    rescue
      err in [ArgumentError] ->
        assert Exception.message(err) =~ "stale type"
        assert PoolRepo.update_all(values, [set: [value: 4]]) == {1, nil}
    else
      result ->
        assert result == {1, []}
    end

    PoolRepo.transaction(fn() ->
      assert PoolRepo.update_all(values, [set: [value: Decimal.new(5)]]) == {1, []}

      assert :ok == down(PoolRepo, 20161112130000, AlterMigrationTwo, log: false)

      assert PoolRepo.update_all(values, [set: [value: 6]]) == {1, []}
    end)

  after
    assert :ok == down(PoolRepo, 20161112120000, AlterMigrationOne, log: false)
  end
end
