Code.require_file "../support/types.exs", __DIR__

defmodule Ecto.Integration.RepoTest do
  use Ecto.Integration.Case, async: Application.get_env(:ecto, :async_integration_tests, true)

  alias Ecto.Integration.TestRepo
  import Ecto.Query

  alias Ecto.Integration.Post
  alias Ecto.Integration.User
  alias Ecto.Integration.Comment
  alias Ecto.Integration.Permalink
  alias Ecto.Integration.Custom
  alias Ecto.Integration.Barebone
  alias Ecto.Integration.CompositePk
  alias Ecto.Integration.PostUsecTimestamps
  alias Ecto.Integration.PostUserCompositePk

  test "returns already started for started repos" do
    assert {:error, {:already_started, _}} = TestRepo.start_link
  end

  test "fetch empty" do
    assert TestRepo.all(Post) == []
    assert TestRepo.all(from p in Post) == []
  end

  test "fetch with in" do
    TestRepo.insert!(%Post{title: "hello"})

    # Works without the query cache.
    assert_raise Ecto.Query.CastError, fn ->
      TestRepo.all(from p in Post, where: p.title in ^nil)
    end

    assert [] = TestRepo.all from p in Post, where: p.title in []
    assert [] = TestRepo.all from p in Post, where: p.title in ["1", "2", "3"]
    assert [] = TestRepo.all from p in Post, where: p.title in ^[]

    assert [_] = TestRepo.all from p in Post, where: not p.title in []
    assert [_] = TestRepo.all from p in Post, where: p.title in ["1", "hello", "3"]
    assert [_] = TestRepo.all from p in Post, where: p.title in ["1", ^"hello", "3"]
    assert [_] = TestRepo.all from p in Post, where: p.title in ^["1", "hello", "3"]

    # Still doesn't work after the query cache.
    assert_raise Ecto.Query.CastError, fn ->
      TestRepo.all(from p in Post, where: p.title in ^nil)
    end
  end

  test "fetch without schema" do
    %Post{} = TestRepo.insert!(%Post{title: "title1"})
    %Post{} = TestRepo.insert!(%Post{title: "title2"})

    assert ["title1", "title2"] =
      TestRepo.all(from(p in "posts", order_by: p.title, select: p.title))

    assert [_] =
      TestRepo.all(from(p in "posts", where: p.title == "title1", select: p.id))
  end

  @tag :invalid_prefix
  test "fetch with invalid prefix" do
    assert catch_error(TestRepo.all("posts", prefix: "oops"))
  end

  test "insert, update and delete" do
    post = %Post{title: "insert, update, delete", text: "fetch empty"}
    meta = post.__meta__

    assert %Post{} = inserted = TestRepo.insert!(post)
    assert %Post{} = updated = TestRepo.update!(Ecto.Changeset.change(inserted, text: "new"))

    deleted_meta = put_in meta.state, :deleted
    assert %Post{__meta__: ^deleted_meta} = TestRepo.delete!(updated)

    loaded_meta = put_in meta.state, :loaded
    assert %Post{__meta__: ^loaded_meta} = TestRepo.insert!(post)

    post = TestRepo.one(Post)
    assert post.__meta__.state == :loaded
    assert post.inserted_at
  end

  test "insert, update and delete with field source" do
    permalink = %Permalink{url: "url"}
    assert %Permalink{url: "url"} = inserted =
           TestRepo.insert!(permalink)
    assert %Permalink{url: "new"} = updated =
           TestRepo.update!(Ecto.Changeset.change(inserted, url: "new"))
    assert %Permalink{url: "new"} =
           TestRepo.delete!(updated)
  end

  @tag :composite_pk
  test "insert, update and delete with composite pk" do
    c1 = TestRepo.insert!(%CompositePk{a: 1, b: 2, name: "first"})
    c2 = TestRepo.insert!(%CompositePk{a: 1, b: 3, name: "second"})

    assert CompositePk |> first |> TestRepo.one == c1
    assert CompositePk |> last |> TestRepo.one == c2

    changeset = Ecto.Changeset.cast(c1, %{name: "first change"}, ~w(name))
    c1 = TestRepo.update!(changeset)
    assert TestRepo.get_by!(CompositePk, %{a: 1, b: 2}) == c1

    TestRepo.delete!(c2)
    assert TestRepo.all(CompositePk) == [c1]

    assert_raise ArgumentError, ~r"to have exactly one primary key", fn ->
      TestRepo.get(CompositePk, [])
    end

    assert_raise ArgumentError, ~r"to have exactly one primary key", fn ->
      TestRepo.get!(CompositePk, [1, 2])
    end
  end

  @tag :composite_pk
  test "insert, update and delete with associated composite pk" do
    user = TestRepo.insert!(%User{})
    post = TestRepo.insert!(%Post{title: "post title", text: "post text"})

    user_post = TestRepo.insert!(%PostUserCompositePk{user_id: user.id, post_id: post.id})
    assert TestRepo.get_by!(PostUserCompositePk, [user_id: user.id, post_id: post.id]) == user_post
    TestRepo.delete!(user_post)
    assert TestRepo.all(PostUserCompositePk) == []
  end

  @tag :invalid_prefix
  test "insert, update and delete with invalid prefix" do
    post = TestRepo.insert!(%Post{})
    changeset = Ecto.Changeset.change(post, title: "foo")
    assert catch_error(TestRepo.insert(%Post{}, prefix: "oops"))
    assert catch_error(TestRepo.update(changeset, prefix: "oops"))
    assert catch_error(TestRepo.delete(changeset, prefix: "oops"))
  end

  test "insert and update with changeset" do
    # On insert we merge the fields and changes
    changeset = Ecto.Changeset.cast(%Post{text: "x", title: "wrong"},
                                    %{"title" => "hello", "temp" => "unknown"}, ~w(title temp))

    post = TestRepo.insert!(changeset)
    assert %Post{text: "x", title: "hello", temp: "unknown"} = post
    assert %Post{text: "x", title: "hello", temp: "temp"} = TestRepo.get!(Post, post.id)

    # On update we merge only fields, direct schema changes are discarded
    changeset = Ecto.Changeset.cast(%{post | text: "y"},
                                    %{"title" => "world", "temp" => "unknown"}, ~w(title temp))

    assert %Post{text: "y", title: "world", temp: "unknown"} = TestRepo.update!(changeset)
    assert %Post{text: "x", title: "world", temp: "temp"} = TestRepo.get!(Post, post.id)
  end

  test "insert and update with empty changeset" do
    # On insert we merge the fields and changes
    changeset = Ecto.Changeset.cast(%Permalink{}, %{}, ~w())
    assert %Permalink{} = permalink = TestRepo.insert!(changeset)

    # Assert we can update the same value twice,
    # without changes, without triggering stale errors.
    changeset = Ecto.Changeset.cast(permalink, %{}, ~w())
    assert TestRepo.update!(changeset) == permalink
    assert TestRepo.update!(changeset) == permalink
  end

  @tag :no_primary_key
  test "insert with no primary key" do
    assert %Barebone{num: nil} = TestRepo.insert!(%Barebone{})
    assert %Barebone{num: 13} = TestRepo.insert!(%Barebone{num: 13})
  end

  @tag :read_after_writes
  test "insert and update with changeset read after writes" do
    defmodule RAW do
      use Ecto.Schema

      schema "comments" do
        field :text, :string
        field :lock_version, :integer, read_after_writes: true
      end
    end

    changeset = Ecto.Changeset.cast(struct(RAW, %{}), %{}, ~w())

    # If the field is nil, we will not send it
    # and read the value back from the database.
    assert %{id: cid, lock_version: 1} = raw = TestRepo.insert!(changeset)

    # Set the counter to 11, so we can read it soon
    TestRepo.update_all from(u in RAW, where: u.id == ^cid), set: [lock_version: 11]

    # We will read back on update too
    changeset = Ecto.Changeset.cast(raw, %{"text" => "0"}, ~w(text))
    assert %{id: ^cid, lock_version: 11, text: "0"} = TestRepo.update!(changeset)
  end

  test "insert autogenerates for custom type" do
    post = TestRepo.insert!(%Post{uuid: nil})
    assert byte_size(post.uuid) == 36
    assert TestRepo.get_by(Post, uuid: post.uuid) == post
  end

  @tag :id_type
  test "insert autogenerates for custom id type" do
    defmodule ID do
      use Ecto.Schema

      @primary_key {:id, Elixir.Custom.Permalink, autogenerate: true}
      schema "posts" do
      end
    end

    id = TestRepo.insert!(struct(ID, id: nil))
    assert id.id
    assert TestRepo.get_by(ID, id: "#{id.id}-hello") == id
  end

  @tag :id_type
  @tag :assigns_id_type
  test "insert with user-assigned primary key" do
    assert %Post{id: 1} = TestRepo.insert!(%Post{id: 1})
  end

  @tag :id_type
  @tag :assigns_id_type
  test "insert and update with user-assigned primary key in changeset" do
    changeset = Ecto.Changeset.cast(%Post{id: 11}, %{"id" => "13"}, ~w(id))
    assert %Post{id: 13} = post = TestRepo.insert!(changeset)

    changeset = Ecto.Changeset.cast(post, %{"id" => "15"}, ~w(id))
    assert %Post{id: 15} = TestRepo.update!(changeset)
  end

  @tag :uses_usec
  test "insert and fetch a schema with timestamps with usec" do
    p1 = TestRepo.insert!(%PostUsecTimestamps{title: "hello"})
    assert [p1] == TestRepo.all(PostUsecTimestamps)
  end

  test "insert and fetch a schema with utc timestamps" do
    datetime = System.system_time(:seconds) * 1_000_000 |> DateTime.from_unix!(:microseconds)
    TestRepo.insert!(%User{inserted_at: datetime})
    assert [%{inserted_at: ^datetime}] = TestRepo.all(User)
  end

  test "optimistic locking in update/delete operations" do
    import Ecto.Changeset, only: [cast: 3, optimistic_lock: 2]
    base_post = TestRepo.insert!(%Comment{})

    cs_ok =
      base_post
      |> cast(%{"text" => "foo.bar"}, ~w(text))
      |> optimistic_lock(:lock_version)
    TestRepo.update!(cs_ok)

    cs_stale = optimistic_lock(base_post, :lock_version)
    assert_raise Ecto.StaleEntryError, fn -> TestRepo.update!(cs_stale) end
    assert_raise Ecto.StaleEntryError, fn -> TestRepo.delete!(cs_stale) end
  end

  @tag :unique_constraint
  test "unique constraint" do
    changeset = Ecto.Changeset.change(%Post{}, uuid: Tds.UUID.generate())
    {:ok, _}  = TestRepo.insert(changeset)

    exception =
      assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn ->
        changeset
        |> TestRepo.insert()
      end

    assert exception.message =~ "unique: posts_uuid_index"
    assert exception.message =~ "The changeset has not defined any constraint."

    message = ~r/constraint error when attempting to insert struct/
    exception =
      assert_raise Ecto.ConstraintError, message, fn ->
        changeset
        |> Ecto.Changeset.unique_constraint(:uuid, name: :posts_email_changeset)
        |> TestRepo.insert()
      end

    assert exception.message =~ "unique: posts_email_changeset"

    {:error, changeset} =
      changeset
      |> Ecto.Changeset.unique_constraint(:uuid)
      |> TestRepo.insert()
    assert changeset.errors == [uuid: {"has already been taken", []}]
    assert changeset.data.__meta__.state == :built
  end

  @tag :unique_constraint
  test "unique constraint from association" do
    uuid = Tds.UUID.generate()
    post = & %Post{} |> Ecto.Changeset.change(uuid: &1) |> Ecto.Changeset.unique_constraint(:uuid)

    {:error, changeset} =
      TestRepo.insert %User{
        comments: [%Comment{}],
        permalink: %Permalink{},
        posts: [post.(uuid), post.(uuid), post.(Tds.UUID.generate)]
      }

    [_, p2, _] = changeset.changes.posts
    assert p2.errors == [uuid: {"has already been taken", []}]
  end

  @tag :id_type
  @tag :unique_constraint
  test "unique constraint with binary_id" do
    changeset = Ecto.Changeset.change(%Custom{}, uuid: Tds.UUID.generate())
    {:ok, _}  = TestRepo.insert(changeset)

    {:error, changeset} =
      changeset
      |> Ecto.Changeset.unique_constraint(:uuid)
      |> TestRepo.insert()
    assert changeset.errors == [uuid: {"has already been taken", []}]
    assert changeset.data.__meta__.state == :built
  end

  test "unique pseudo-constraint violation error message with join table at the repository" do
    post =
      TestRepo.insert!(%Post{title: "some post"})
      |> TestRepo.preload(:unique_users)

    user =
      TestRepo.insert!(%User{name: "some user"})

    # Violate the unique composite index
    {:error, changeset} =
      post
      |> Ecto.Changeset.change
      |> Ecto.Changeset.put_assoc(:unique_users, [user, user])
      |> TestRepo.update

    errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
    assert errors == %{unique_users: [%{}, %{id: ["has already been taken"]}]}
    refute changeset.valid?
  end

  @tag :join
  @tag :unique_constraint
  test "unique constraint violation error message with join table in single changeset" do
    post =
      TestRepo.insert!(%Post{title: "some post"})
      |> TestRepo.preload(:constraint_users)

    user =
      TestRepo.insert!(%User{name: "some user"})

    # Violate the unique composite index
    {:error, changeset} =
      post
      |> Ecto.Changeset.change
      |> Ecto.Changeset.put_assoc(:constraint_users, [user, user])
      |> Ecto.Changeset.unique_constraint(:user,
          name: :posts_users_composite_pk_post_id_user_id_index,
          message: "has already been assigned")
      |> TestRepo.update

    errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
    assert errors == %{constraint_users: [%{}, %{user: ["has already been assigned"]}]}

    refute changeset.valid?
  end

  @tag :join
  @tag :unique_constraint
  test "unique constraint violation error message with join table and separate changesets" do
    post =
      TestRepo.insert!(%Post{title: "some post"})
      |> TestRepo.preload(:constraint_users)

    user = TestRepo.insert!(%User{name: "some user"})

    post
    |> Ecto.Changeset.change
    |> Ecto.Changeset.put_assoc(:constraint_users, [user])
    |> TestRepo.update

    # Violate the unique composite index
    {:error, changeset} =
      post
      |> Ecto.Changeset.change
      |> Ecto.Changeset.put_assoc(:constraint_users, [user])
      |> Ecto.Changeset.unique_constraint(:user,
          name: :posts_users_composite_pk_post_id_user_id_index,
          message: "has already been assigned")
      |> TestRepo.update

    errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
    assert errors == %{constraint_users: [%{user: ["has already been assigned"]}]}

    refute changeset.valid?
  end

  @tag :foreign_key_constraint
  test "foreign key constraint" do
    changeset = Ecto.Changeset.change(%Comment{post_id: 0})

    exception =
      assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn ->
        changeset
        |> TestRepo.insert()
      end

    assert exception.message =~ "foreign_key: comments_post_id_fkey"
    assert exception.message =~ "The changeset has not defined any constraint."

    message = ~r/constraint error when attempting to insert struct/
    exception =
      assert_raise Ecto.ConstraintError, message, fn ->
        changeset
        |> Ecto.Changeset.foreign_key_constraint(:post_id, name: :comments_post_id_other)
        |> TestRepo.insert()
      end

    assert exception.message =~ "foreign_key: comments_post_id_other"

    {:error, changeset} =
      changeset
      |> Ecto.Changeset.foreign_key_constraint(:post_id)
      |> TestRepo.insert()
    assert changeset.errors == [post_id: {"does not exist", []}]
  end

  @tag :foreign_key_constraint
  test "assoc constraint" do
    changeset = Ecto.Changeset.change(%Comment{post_id: 0})

    exception =
      assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn ->
        changeset
        |> TestRepo.insert()
      end

    assert exception.message =~ "foreign_key: comments_post_id_fkey"
    assert exception.message =~ "The changeset has not defined any constraint."

    message = ~r/constraint error when attempting to insert struct/
    exception =
      assert_raise Ecto.ConstraintError, message, fn ->
        changeset
        |> Ecto.Changeset.assoc_constraint(:post, name: :comments_post_id_other)
        |> TestRepo.insert()
      end

    assert exception.message =~ "foreign_key: comments_post_id_other"

    {:error, changeset} =
      changeset
      |> Ecto.Changeset.assoc_constraint(:post)
      |> TestRepo.insert()
    assert changeset.errors == [post: {"does not exist", []}]
  end

  @tag :foreign_key_constraint
  test "no assoc constraint error" do
    user = TestRepo.insert!(%User{})
    TestRepo.insert!(%Permalink{user_id: user.id})

    exception =
      assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to delete struct/, fn ->
        TestRepo.delete!(user)
      end

    assert exception.message =~ "foreign_key: permalinks_user_id_fkey"
    assert exception.message =~ "The changeset has not defined any constraint."
  end

  @tag :foreign_key_constraint
  test "no assoc constraint with changeset mismatch" do
    user = TestRepo.insert!(%User{})
    TestRepo.insert!(%Permalink{user_id: user.id})

    message = ~r/constraint error when attempting to delete struct/
    exception =
      assert_raise Ecto.ConstraintError, message, fn ->
        user
        |> Ecto.Changeset.change
        |> Ecto.Changeset.no_assoc_constraint(:permalink, name: :permalinks_user_id_pther)
        |> TestRepo.delete()
      end

    assert exception.message =~ "foreign_key: permalinks_user_id_pther"
  end

  @tag :foreign_key_constraint
  test "no assoc constraint with changeset match" do
    user = TestRepo.insert!(%User{})
    TestRepo.insert!(%Permalink{user_id: user.id})

    {:error, changeset} =
      user
      |> Ecto.Changeset.change
      |> Ecto.Changeset.no_assoc_constraint(:permalink)
      |> TestRepo.delete()
    assert changeset.errors == [permalink: {"is still associated with this entry", []}]
  end

  @tag :add_contraint
  test "insert and update with failing child foreign key" do
    defmodule Order do
      use Ecto.Integration.Schema
      import Ecto.Changeset

      schema "orders" do
        embeds_one :item, Ecto.Integration.Item
        belongs_to :comment, Ecto.Integration.Comment
      end

      def changeset(order, params) do
        order
        |> cast(params, [:comment_id])
        |> cast_embed(:item, with: &item_changeset/2)
        |> cast_assoc(:comment, with: &comment_changeset/2)
      end

      def item_changeset(item, params) do
        item
        |> cast(params, [:price])
      end

      def comment_changeset(comment, params) do
        comment
        |> cast(params, [:post_id, :text])
        # todo: this should be |> foreign_key_constraint(:post_id, name: "FK__comments_post_id")
        |> unique_constraint(:post_id, name: "FK__comments_post_id")
        |> cast_assoc(:post)
        |> assoc_constraint(:post)
      end
    end

    changeset = Order.changeset(struct(Order, %{}), %{item: %{price: 10}, comment: %{text: "1", post_id: 0}})

    assert %Ecto.Changeset{} = changeset.changes.item

    {:error, changeset} = TestRepo.insert(changeset)
    assert %Ecto.Changeset{} = changeset.changes.item

    order = TestRepo.insert!(Order.changeset(struct(Order, %{}), %{}))
    |> TestRepo.preload([:comment])

    changeset = Order.changeset(order, %{item: %{price: 10}, comment: %{text: "1", post_id: 0}})

    assert %Ecto.Changeset{} = changeset.changes.item

    {:error, changeset} = TestRepo.update(changeset)
    assert %Ecto.Changeset{} = changeset.changes.item
  end

  test "unsafe_validate_unique/3" do
    {:ok, inserted_post} = TestRepo.insert(%Post{title: "Greetings", text: "hi"})
    new_post_changeset = Post.changeset(%Post{}, %{title: "Greetings", text: "ho"})

    changeset = Ecto.Changeset.unsafe_validate_unique(new_post_changeset, [:title], TestRepo)
    assert changeset.errors[:title] ==
           {"has already been taken", validation: :unsafe_unique, fields: [:title]}

    changeset = Ecto.Changeset.unsafe_validate_unique(new_post_changeset, [:title, :text], TestRepo)
    assert changeset.errors[:title] == nil

    update_changeset = Post.changeset(inserted_post, %{text: "ho"})
    changeset = Ecto.Changeset.unsafe_validate_unique(update_changeset, [:title], TestRepo)
    assert changeset.errors[:title] == nil # cannot conflict with itself
  end

  test "get(!)" do
    post1 = TestRepo.insert!(%Post{title: "1", text: "hai"})
    post2 = TestRepo.insert!(%Post{title: "2", text: "hai"})

    assert post1 == TestRepo.get(Post, post1.id)
    assert post2 == TestRepo.get(Post, to_string post2.id) # With casting

    assert post1 == TestRepo.get!(Post, post1.id)
    assert post2 == TestRepo.get!(Post, to_string post2.id) # With casting

    TestRepo.delete!(post1)

    assert nil   == TestRepo.get(Post, post1.id)
    assert_raise Ecto.NoResultsError, fn ->
      TestRepo.get!(Post, post1.id)
    end
  end

  test "get(!) with custom source" do
    custom = Ecto.put_meta(%Custom{}, source: "posts")
    custom = TestRepo.insert!(custom)
    bid    = custom.bid
    assert %Custom{bid: ^bid, __meta__: %{source: {nil, "posts"}}} =
           TestRepo.get(from(c in {"posts", Custom}), bid)
  end

  test "get(!) with binary_id" do
    custom = TestRepo.insert!(%Custom{})
    bid = custom.bid
    assert %Custom{bid: ^bid} = TestRepo.get(Custom, bid)
  end

  test "get_by(!)" do
    post1 = TestRepo.insert!(%Post{title: "1", text: "hai"})
    post2 = TestRepo.insert!(%Post{title: "2", text: "hello"})

    assert post1 == TestRepo.get_by(Post, id: post1.id)
    assert post1 == TestRepo.get_by(Post, text: post1.text)
    assert post1 == TestRepo.get_by(Post, id: post1.id, text: post1.text)
    assert post2 == TestRepo.get_by(Post, id: to_string(post2.id)) # With casting
    assert nil   == TestRepo.get_by(Post, text: "hey")
    assert nil   == TestRepo.get_by(Post, id: post2.id, text: "hey")

    assert post1 == TestRepo.get_by!(Post, id: post1.id)
    assert post1 == TestRepo.get_by!(Post, text: post1.text)
    assert post1 == TestRepo.get_by!(Post, id: post1.id, text: post1.text)
    assert post2 == TestRepo.get_by!(Post, id: to_string(post2.id)) # With casting

    assert post1 == TestRepo.get_by!(Post, %{id: post1.id})

    assert_raise Ecto.NoResultsError, fn ->
      TestRepo.get_by!(Post, id: post2.id, text: "hey")
    end
  end

  test "first, last and one(!)" do
    post1 = TestRepo.insert!(%Post{title: "1", text: "hai"})
    post2 = TestRepo.insert!(%Post{title: "2", text: "hai"})

    assert post1 == Post |> first |> TestRepo.one
    assert post2 == Post |> last |> TestRepo.one

    query = from p in Post, order_by: p.title
    assert post1 == query |> first |> TestRepo.one
    assert post2 == query |> last |> TestRepo.one

    query = from p in Post, order_by: [desc: p.title], limit: 10
    assert post2 == query |> first |> TestRepo.one
    assert post1 == query |> last |> TestRepo.one

    query = from p in Post, where: is_nil(p.id)
    refute query |> first |> TestRepo.one
    refute query |> first |> TestRepo.one
    assert_raise Ecto.NoResultsError, fn -> query |> first |> TestRepo.one! end
    assert_raise Ecto.NoResultsError, fn -> query |> last |> TestRepo.one! end
  end

  test "aggregate" do
    assert TestRepo.aggregate(Post, :max, :visits) == nil

    TestRepo.insert!(%Post{visits: 10})
    TestRepo.insert!(%Post{visits: 12})
    TestRepo.insert!(%Post{visits: 14})
    TestRepo.insert!(%Post{visits: 14})

    # Barebones
    assert TestRepo.aggregate(Post, :max, :visits) == 14
    assert TestRepo.aggregate(Post, :min, :visits) == 10
    assert TestRepo.aggregate(Post, :count, :visits) == 4
    assert "50" = to_string(TestRepo.aggregate(Post, :sum, :visits))
    assert "12" <> _ = to_string(TestRepo.aggregate(Post, :avg, :visits))

    # With order_by
    query = from Post, order_by: [asc: :visits]
    assert TestRepo.aggregate(query, :max, :visits) == 14

    # With order_by and limit
    query = from Post, order_by: [asc: :visits], limit: 2
    assert TestRepo.aggregate(query, :max, :visits) == 12

    # With distinct
    query = from Post, order_by: [asc: :visits], distinct: true
    assert TestRepo.aggregate(query, :count, :visits) == 3
  end

  test "insert all" do
    assert {2, nil} = TestRepo.insert_all("comments", [[text: "1"], %{text: "2", lock_version: 2}])
    assert {2, nil} = TestRepo.insert_all({"comments", Comment}, [[text: "3"], %{text: "4", lock_version: 2}])
    assert [%Comment{text: "1", lock_version: 1},
            %Comment{text: "2", lock_version: 2},
            %Comment{text: "3", lock_version: 1},
            %Comment{text: "4", lock_version: 2}] = TestRepo.all(Comment)

    assert {2, []} = TestRepo.insert_all(Post, [[], []])
    assert [%Post{}, %Post{}] = TestRepo.all(Post)

    assert {0, []} = TestRepo.insert_all("posts", [])
    assert {0, []} = TestRepo.insert_all({"posts", Post}, [])
  end

  @tag :invalid_prefix
  test "insert all with invalid prefix" do
    assert catch_error(TestRepo.insert_all(Post, [[], []], prefix: "oops"))
  end

  @tag :returning
  test "insert all with returning with schema" do
    assert {0, []} = TestRepo.insert_all(Comment, [], returning: true)
    assert {0, nil} = TestRepo.insert_all(Comment, [], returning: false)

    {2, [c1, c2]} = TestRepo.insert_all(Comment, [[text: "1"], [text: "2"]], returning: [:id, :text])
    assert %Comment{text: "1", __meta__: %{state: :loaded}} = c1
    assert %Comment{text: "2", __meta__: %{state: :loaded}} = c2

    {2, [c1, c2]} = TestRepo.insert_all(Comment, [[text: "3"], [text: "4"]], returning: true)
    assert %Comment{text: "3", __meta__: %{state: :loaded}} = c1
    assert %Comment{text: "4", __meta__: %{state: :loaded}} = c2
  end

  @tag :returning
  test "insert all with returning with schema with field source" do
    assert {0, []} = TestRepo.insert_all(Permalink, [], returning: true)
    assert {0, nil} = TestRepo.insert_all(Permalink, [], returning: false)

    {2, [c1, c2]} = TestRepo.insert_all(Permalink, [[url: "1"], [url: "2"]], returning: [:id, :url])
    assert %Permalink{url: "1", __meta__: %{state: :loaded}} = c1
    assert %Permalink{url: "2", __meta__: %{state: :loaded}} = c2

    {2, [c1, c2]} = TestRepo.insert_all(Permalink, [[url: "3"], [url: "4"]], returning: true)
    assert %Permalink{url: "3", __meta__: %{state: :loaded}} = c1
    assert %Permalink{url: "4", __meta__: %{state: :loaded}} = c2
  end

  @tag :returning
  test "insert all with returning without schema" do
    {2, [c1, c2]} = TestRepo.insert_all("comments", [[text: "1"], [text: "2"]], returning: [:id, :text])
    assert %{id: _, text: "1"} = c1
    assert %{id: _, text: "2"} = c2

    assert_raise ArgumentError, fn ->
      TestRepo.insert_all("comments", [[text: "1"], [text: "2"]], returning: true)
    end
  end

  test "insert all with dumping" do
    datetime = ~N[2014-01-16 20:26:51.000000]
    assert {2, []} = TestRepo.insert_all(Post, [%{inserted_at: datetime}, %{title: "date"}])
    assert [%Post{inserted_at: ^datetime, title: nil},
            %Post{inserted_at: nil, title: "date"}] = TestRepo.all(Post)
  end

  test "insert all autogenerates for binary_id type" do
    custom = TestRepo.insert!(%Custom{bid: nil})
    assert custom.bid
    assert TestRepo.get(Custom, custom.bid)
    assert TestRepo.delete!(custom)
    refute TestRepo.get(Custom, custom.bid)

    uuid = Tds.UUID.generate
    assert {2, nil} = TestRepo.insert_all(Custom, [%{uuid: uuid}, %{bid: custom.bid}])
    assert [%Custom{bid: bid2, uuid: nil},
            %Custom{bid: bid1, uuid: ^uuid}] = Enum.sort_by(TestRepo.all(Custom), & &1.uuid)
    assert bid1 && bid2
    assert custom.bid != bid1
    assert custom.bid == bid2
  end

  test "update all" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
    assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
    assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})

    assert {3, []} = TestRepo.update_all(Post, set: [title: "x"])

    assert %Post{title: "x"} = TestRepo.get(Post, id1)
    assert %Post{title: "x"} = TestRepo.get(Post, id2)
    assert %Post{title: "x"} = TestRepo.get(Post, id3)

    assert {3, []} = TestRepo.update_all("posts", [set: [title: nil]], returning: false)

    assert %Post{title: nil} = TestRepo.get(Post, id1)
    assert %Post{title: nil} = TestRepo.get(Post, id2)
    assert %Post{title: nil} = TestRepo.get(Post, id3)
  end

  @tag :invalid_prefix
  test "update all with invalid prefix" do
    assert catch_error(TestRepo.update_all(Post, [set: [title: "x"]], prefix: "oops"))
  end

  @tag :returning
  test "update all with returning with schema" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
    assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
    assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})

    assert {3, posts} = TestRepo.update_all(Post, [set: [title: "x"]], returning: true)

    [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
    assert %Post{id: ^id1, title: "x"} = p1
    assert %Post{id: ^id2, title: "x"} = p2
    assert %Post{id: ^id3, title: "x"} = p3

    assert {3, posts} = TestRepo.update_all(Post, [set: [visits: 11]], returning: [:id, :visits])

    [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
    assert %Post{id: ^id1, title: nil, visits: 11} = p1
    assert %Post{id: ^id2, title: nil, visits: 11} = p2
    assert %Post{id: ^id3, title: nil, visits: 11} = p3
  end

  @tag :returning
  test "update all with returning without schema" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
    assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
    assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})

    assert {3, posts} = TestRepo.update_all("posts", [set: [title: "x"]], returning: [:id, :title])

    [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
    assert p1 == %{id: id1, title: "x"}
    assert p2 == %{id: id2, title: "x"}
    assert p3 == %{id: id3, title: "x"}
  end

  test "update all with filter" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
    assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
    assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})

    query = from(p in Post, where: p.title == "1" or p.title == "2",
                            update: [set: [text: ^"y"]])
    assert {2, []} = TestRepo.update_all(query, set: [title: "x"])

    assert %Post{title: "x", text: "y"} = TestRepo.get(Post, id1)
    assert %Post{title: "x", text: "y"} = TestRepo.get(Post, id2)
    assert %Post{title: "3", text: nil} = TestRepo.get(Post, id3)
  end

  test "update all no entries" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
    assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
    assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})

    query = from(p in Post, where: p.title == "4")
    assert {0, []} = TestRepo.update_all(query, set: [title: "x"])

    assert %Post{title: "1"} = TestRepo.get(Post, id1)
    assert %Post{title: "2"} = TestRepo.get(Post, id2)
    assert %Post{title: "3"} = TestRepo.get(Post, id3)
  end

  test "update all increment syntax" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1", visits: 0})
    assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2", visits: 1})

    # Positive
    query = from p in Post, where: not is_nil(p.id), update: [inc: [visits: 2]]
    assert {2, []} = TestRepo.update_all(query, [])

    assert %Post{visits: 2} = TestRepo.get(Post, id1)
    assert %Post{visits: 3} = TestRepo.get(Post, id2)

    # Negative
    query = from p in Post, where: not is_nil(p.id), update: [inc: [visits: -1]]
    assert {2, []} = TestRepo.update_all(query, [])

    assert %Post{visits: 1} = TestRepo.get(Post, id1)
    assert %Post{visits: 2} = TestRepo.get(Post, id2)
  end

  @tag :id_type
  test "update all with casting and dumping on id type field" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{})
    assert {1, []} = TestRepo.update_all(Post, set: [counter: to_string(id1)])
    assert %Post{counter: ^id1} = TestRepo.get(Post, id1)
  end

  test "update all with casting and dumping" do
    text = "hai"
    datetime = ~N[2014-01-16 20:26:51.000000]
    assert %Post{id: id} = TestRepo.insert!(%Post{})

    assert {1, []} = TestRepo.update_all(Post, set: [text: text, inserted_at: datetime])
    assert %Post{text: "hai", inserted_at: ^datetime} = TestRepo.get(Post, id)
  end

  test "delete all" do
    assert %Post{} = TestRepo.insert!(%Post{title: "1", text: "hai"})
    assert %Post{} = TestRepo.insert!(%Post{title: "2", text: "hai"})
    assert %Post{} = TestRepo.insert!(%Post{title: "3", text: "hai"})

    assert {3, []} = TestRepo.delete_all(Post, returning: false)
    assert [] = TestRepo.all(Post)
  end

  @tag :invalid_prefix
  test "delete all with invalid prefix" do
    assert catch_error(TestRepo.delete_all(Post, prefix: "oops"))
  end

  @tag :returning
  test "delete all with returning with schema" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1", text: "hai"})
    assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2", text: "hai"})
    assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3", text: "hai"})

    assert {3, posts} = TestRepo.delete_all(Post, returning: true)

    [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
    assert %Post{id: ^id1, title: "1"} = p1
    assert %Post{id: ^id2, title: "2"} = p2
    assert %Post{id: ^id3, title: "3"} = p3
  end

  @tag :returning
  test "delete all with returning without schema" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1", text: "hai"})
    assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2", text: "hai"})
    assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3", text: "hai"})

    assert {3, posts} = TestRepo.delete_all("posts", returning: [:id, :title])

    [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
    assert p1 == %{id: id1, title: "1"}
    assert p2 == %{id: id2, title: "2"}
    assert p3 == %{id: id3, title: "3"}
  end

  test "delete all with filter" do
    assert %Post{} = TestRepo.insert!(%Post{title: "1", text: "hai"})
    assert %Post{} = TestRepo.insert!(%Post{title: "2", text: "hai"})
    assert %Post{} = TestRepo.insert!(%Post{title: "3", text: "hai"})

    query = from(p in Post, where: p.title == "1" or p.title == "2")
    assert {2, []} = TestRepo.delete_all(query)
    assert [%Post{}] = TestRepo.all(Post)
  end

  test "delete all no entries" do
    assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1", text: "hai"})
    assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2", text: "hai"})
    assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3", text: "hai"})

    query = from(p in Post, where: p.title == "4")
    assert {0, []} = TestRepo.delete_all(query)
    assert %Post{title: "1"} = TestRepo.get(Post, id1)
    assert %Post{title: "2"} = TestRepo.get(Post, id2)
    assert %Post{title: "3"} = TestRepo.get(Post, id3)
  end

  test "virtual field" do
    assert %Post{id: id} = TestRepo.insert!(%Post{title: "1", text: "hai"})
    assert TestRepo.get(Post, id).temp == "temp"
  end

  ## Query syntax

  defmodule Foo do
    defstruct [:title]
  end

  describe "query select" do
    test "expressions" do
      %Post{} = TestRepo.insert!(%Post{title: "1", text: "hai"})

      assert [{"1", "hai"}] ==
             TestRepo.all(from p in Post, select: {p.title, p.text})

      assert [["1", "hai"]] ==
             TestRepo.all(from p in Post, select: [p.title, p.text])

      assert [%{:title => "1", 3 => "hai", "text" => "hai"}] ==
             TestRepo.all(from p in Post, select: %{
               :title => p.title,
               "text" => p.text,
               3 => p.text
             })

      assert [%{:title => "1", "1" => "hai", "text" => "hai"}] ==
             TestRepo.all(from p in Post, select: %{
               :title  => p.title,
               p.title => p.text,
               "text"  => p.text
             })

      assert [%Foo{title: "1"}] ==
             TestRepo.all(from p in Post, select: %Foo{title: p.title})
    end

    test "map update" do
      %Post{} = TestRepo.insert!(%Post{title: "1", text: "hai"})

      assert [%Post{:title => "new title", text: "hai"}] =
             TestRepo.all(from p in Post, select: %{p | title: "new title"})

      assert [%Post{title: "new title", text: "hai"}] =
        TestRepo.all(from p in Post, select: %Post{p | title: "new title"})

      assert_raise KeyError, fn ->
        TestRepo.all(from p in Post, select: %{p | unknown: "new title"})
      end

      assert_raise BadMapError, fn ->
        TestRepo.all(from p in Post, select: %{p.title | title: "new title"})
      end

      assert_raise BadStructError, fn ->
        TestRepo.all(from p in Post, select: %Foo{p | title: p.title})
      end
    end

    test "take with structs" do
      %{id: pid1} = TestRepo.insert!(%Post{title: "1"})
      %{id: pid2} = TestRepo.insert!(%Post{title: "2"})
      %{id: pid3} = TestRepo.insert!(%Post{title: "3"})

      [p1, p2, p3] = Post |> select([p], struct(p, [:title])) |> order_by([:title]) |> TestRepo.all
      refute p1.id
      assert p1.title == "1"
      assert match?(%Post{}, p1)
      refute p2.id
      assert p2.title == "2"
      assert match?(%Post{}, p2)
      refute p3.id
      assert p3.title == "3"
      assert match?(%Post{}, p3)

      [p1, p2, p3] = Post |> select([:id]) |> order_by([:id]) |> TestRepo.all
      assert %Post{id: ^pid1} = p1
      assert %Post{id: ^pid2} = p2
      assert %Post{id: ^pid3} = p3
    end

    test "take with maps" do
      %{id: pid1} = TestRepo.insert!(%Post{title: "1"})
      %{id: pid2} = TestRepo.insert!(%Post{title: "2"})
      %{id: pid3} = TestRepo.insert!(%Post{title: "3"})

      [p1, p2, p3] = "posts" |> select([p], map(p, [:title])) |> order_by([:title]) |> TestRepo.all
      assert p1 == %{title: "1"}
      assert p2 == %{title: "2"}
      assert p3 == %{title: "3"}

      [p1, p2, p3] = "posts" |> select([:id]) |> order_by([:id]) |> TestRepo.all
      assert p1 == %{id: pid1}
      assert p2 == %{id: pid2}
      assert p3 == %{id: pid3}
    end

    test "take with preload assocs" do
      %{id: pid} = TestRepo.insert!(%Post{title: "post"})
      TestRepo.insert!(%Comment{post_id: pid, text: "comment"})
      fields = [:id, :title, comments: [:text, :post_id]]

      [p] = Post |> preload(:comments) |> select([p], ^fields) |> TestRepo.all
      assert %Post{title: "post"} = p
      assert [%Comment{text: "comment"}] = p.comments

      [p] = Post |> preload(:comments) |> select([p], struct(p, ^fields)) |> TestRepo.all
      assert %Post{title: "post"} = p
      assert [%Comment{text: "comment"}] = p.comments

      [p] = Post |> preload(:comments) |> select([p], map(p, ^fields)) |> TestRepo.all
      assert p == %{id: pid, title: "post", comments: [%{text: "comment", post_id: pid}]}
    end

    test "take with nil preload assoc" do
      %{id: cid} = TestRepo.insert!(%Comment{text: "comment"})
      fields = [:id, :text, post: [:title]]

      [c] = Comment |> preload(:post) |> select([c], ^fields) |> TestRepo.all
      assert %Comment{id: ^cid, text: "comment", post: nil} = c

      [c] = Comment |> preload(:post) |> select([c], struct(c, ^fields)) |> TestRepo.all
      assert %Comment{id: ^cid, text: "comment", post: nil} = c

      [c] = Comment |> preload(:post) |> select([c], map(c, ^fields)) |> TestRepo.all
      assert c == %{id: cid, text: "comment", post: nil}
    end

    test "take with join assocs" do
      %{id: pid} = TestRepo.insert!(%Post{title: "post"})
      %{id: cid} = TestRepo.insert!(%Comment{post_id: pid, text: "comment"})
      fields = [:id, :title, comments: [:text, :post_id, :id]]
      query = from p in Post, where: p.id == ^pid, join: c in assoc(p, :comments), preload: [comments: c]

      p = TestRepo.one(from q in query, select: ^fields)
      assert %Post{title: "post"} = p
      assert [%Comment{text: "comment"}] = p.comments

      p = TestRepo.one(from q in query, select: struct(q, ^fields))
      assert %Post{title: "post"} = p
      assert [%Comment{text: "comment"}] = p.comments

      p = TestRepo.one(from q in query, select: map(q, ^fields))
      assert p == %{id: pid, title: "post", comments: [%{text: "comment", post_id: pid, id: cid}]}
    end

    test "take with single nil column" do
      %Post{} = TestRepo.insert!(%Post{title: "1", counter: nil})
      assert %{counter: nil} =
             TestRepo.one(from p in Post, where: p.title == "1", select: [:counter])
    end

    test "field source" do
      TestRepo.insert!(%Permalink{url: "url"})
      assert ["url"] = Permalink |> select([p], p.url) |> TestRepo.all()
      assert [1] = Permalink |> select([p], count(p.url)) |> TestRepo.all()
    end

    test "merge" do
      %Post{} = TestRepo.insert!(%Post{title: "1", counter: nil})

      # Merge on source
      assert [%Post{title: "2"}] =
             Post |> select([p], merge(p, %{title: "2"})) |> TestRepo.all()
      assert [%Post{title: "2"}] =
             Post |> select([p], p) |> select_merge([p], %{title: "2"}) |> TestRepo.all()

      # Merge on struct
      assert [%Post{title: "2"}] =
             Post |> select([p], merge(%Post{title: p.title}, %Post{title: "2"})) |> TestRepo.all()
      assert [%Post{title: "2"}] =
             Post |> select([p], %Post{title: p.title}) |> select_merge([p], %Post{title: "2"}) |> TestRepo.all()

      assert [%Post{title: "2"}] =
             Post |> select([p], merge(%Post{title: p.title}, %{title: "2"})) |> TestRepo.all()
      assert [%Post{title: "2"}] =
             Post |> select([p], %Post{title: p.title}) |> select_merge([p], %{title: "2"}) |> TestRepo.all()

      # Merge on map
      assert [%{title: "2"}] =
             Post |> select([p], merge(%{title: p.title}, %{title: "2"})) |> TestRepo.all()
      assert [%{title: "2"}] =
             Post |> select([p], %{title: p.title}) |> select_merge([p], %{title: "2"}) |> TestRepo.all()

      # Merge errors
      assert_raise ArgumentError,
                   ~r/can only merge with a struct on the right side when both sides represent the same struct/, fn ->
        Post |> select([p], merge(%{title: p.title}, %Post{title: "2"})) |> TestRepo.all()
      end

      assert_raise ArgumentError,
                   ~r/cannot merge because the left side is not a map/, fn ->
        Post |> select([p], merge(p.title, %{title: "2"})) |> TestRepo.all()
      end

      assert_raise ArgumentError,
                   ~r/cannot merge because the right side is not a map/, fn ->
        Post |> select([p], merge(%{title: "2"}, p.title)) |> TestRepo.all()
      end
    end
  end

  test "query count distinct" do
    TestRepo.insert!(%Post{title: "1"})
    TestRepo.insert!(%Post{title: "1"})
    TestRepo.insert!(%Post{title: "2"})

    assert [3] == Post |> select([p], count(p.title)) |> TestRepo.all
    assert [2] == Post |> select([p], count(p.title, :distinct)) |> TestRepo.all
  end

  test "query where interpolation" do
    post1 = TestRepo.insert!(%Post{text: "x", title: "hello"})
    post2 = TestRepo.insert!(%Post{text: "y", title: "goodbye"})

    assert [post1, post2] == Post |> where([], []) |> TestRepo.all |> Enum.sort_by(& &1.id)
    assert [post1]        == Post |> where([], [title: "hello"]) |> TestRepo.all
    assert [post1]        == Post |> where([], [title: "hello", id: ^post1.id]) |> TestRepo.all

    params0 = []
    params1 = [title: "hello"]
    params2 = [title: "hello", id: post1.id]
    assert [post1, post2]  == (from Post, where: ^params0) |> TestRepo.all |> Enum.sort_by(& &1.id)
    assert [post1]         == (from Post, where: ^params1) |> TestRepo.all
    assert [post1]         == (from Post, where: ^params2) |> TestRepo.all

    post3 = TestRepo.insert!(%Post{text: "y", title: "goodbye", uuid: nil})
    params3 = [title: "goodbye", uuid: post3.uuid]
    assert [post3] == (from Post, where: ^params3) |> TestRepo.all
  end

  ## Logging

  test "log entry logged on query" do
    log = fn entry ->
      assert %Ecto.LogEntry{result: {:ok, _}} = entry
      assert is_integer(entry.query_time) and entry.query_time >= 0
      assert is_integer(entry.decode_time) and entry.query_time >= 0
      assert is_integer(entry.queue_time) and entry.queue_time >= 0
      send(self(), :logged)
    end
    Process.put(:on_log, log)

    _ = TestRepo.all(Post)
    assert_received :logged
  end

  test "log entry not logged when log is false" do
    Process.put(:on_log, fn _ -> flunk("logged") end)
    TestRepo.insert!(%Post{title: "1"}, [log: false])
  end

  describe "upsert via insert" do
    @describetag :upsert

    test "on conflict raise" do
      {:ok, inserted} = TestRepo.insert(%Post{title: "first"}, on_conflict: :raise)
      assert catch_error(TestRepo.insert(%Post{id: inserted.id, title: "second"}, on_conflict: :raise))
    end

    test "on conflict ignore" do
      post = %Post{title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: :nothing)
      assert inserted.id
      assert inserted.__meta__.state == :loaded

      {:ok, not_inserted} = TestRepo.insert(post, on_conflict: :nothing)
      assert not_inserted.id == nil
      assert not_inserted.__meta__.state == :loaded
    end

    @tag :with_conflict_target
    test "on conflict and associations" do
      on_conflict = [set: [title: "second"]]
      post = %Post{uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e",
                   title: "first", comments: [%Comment{}]}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
      assert inserted.id
    end

    @tag :with_conflict_target
    test "on conflict with inc" do
      uuid = "6fa459ea-ee8a-3ca4-894e-db77e160355e"
      post = %Post{title: "first", uuid: uuid}
      {:ok, _} = TestRepo.insert(post)
      post = %{title: "upsert", uuid: uuid}
      TestRepo.insert_all(Post, [post], on_conflict: [inc: [visits: 1]], conflict_target: :uuid)
    end

    @tag :with_conflict_target
    test "on conflict ignore and conflict target" do
      post = %Post{title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:uuid])
      assert inserted.id

      # Error on non-conflict target
      assert catch_error(TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:id]))

      # Error on conflict target
      {:ok, not_inserted} = TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:uuid])
      assert not_inserted.id == nil
    end

    @tag :without_conflict_target
    test "on conflict keyword list" do
      on_conflict = [set: [title: "second"]]
      post = %Post{title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict)
      assert inserted.id

      {:ok, updated} = TestRepo.insert(post, on_conflict: on_conflict)
      assert updated.id == inserted.id
      assert updated.title != "second"
      assert TestRepo.get!(Post, inserted.id).title == "second"
    end

    @tag :with_conflict_target
    test "on conflict keyword list and conflict target" do
      on_conflict = [set: [title: "second"]]
      post = %Post{title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
      assert inserted.id

      # Error on non-conflict target
      assert catch_error(TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:id]))

      {:ok, updated} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
      assert updated.id == inserted.id
      assert updated.title != "second"
      assert TestRepo.get!(Post, inserted.id).title == "second"
    end

    @tag :returning
    @tag :with_conflict_target
    test "on conflict keyword list and conflict target and returning" do
      {:ok, c1} = TestRepo.insert(%Post{})
      {:ok, c2} = TestRepo.insert(%Post{id: c1.id}, on_conflict: [set: [id: c1.id]], conflict_target: [:id], returning: [:id, :uuid])
      {:ok, c3} = TestRepo.insert(%Post{id: c1.id}, on_conflict: [set: [id: c1.id]], conflict_target: [:id], returning: true)
      {:ok, c4} = TestRepo.insert(%Post{id: c1.id}, on_conflict: [set: [id: c1.id]], conflict_target: [:id], returning: false)

      assert c2.uuid == c1.uuid
      assert c3.uuid == c1.uuid
      assert c4.uuid != c1.uuid
    end

    @tag :with_conflict_target
    @tag :with_conflict_target_on_constraint
    test "on conflict keyword list and conflict target on constraint" do
      on_conflict = [set: [title: "new"]]
      post = %Post{title: "old"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: {:constraint, :posts_pkey})
      assert inserted.id

      {:ok, updated} = TestRepo.insert(%{post | id: inserted.id}, on_conflict: on_conflict, conflict_target: {:constraint, :posts_pkey})
      assert updated.id == inserted.id
      assert updated.title != "new"
      assert TestRepo.get!(Post, inserted.id).title == "new"
    end

    @tag :returning
    @tag :with_conflict_target
    test "on conflict keyword list and conflict target and returning and field source" do
      TestRepo.insert!(%Permalink{url: "old"})
      {:ok, c1} = TestRepo.insert(%Permalink{url: "old"},
                                  on_conflict: [set: [url: "new1"]],
                                  conflict_target: [:url],
                                  returning: [:url])

      TestRepo.insert!(%Permalink{url: "old"})
      {:ok, c2} = TestRepo.insert(%Permalink{url: "old"},
                                  on_conflict: [set: [url: "new2"]],
                                  conflict_target: [:url],
                                  returning: true)

      assert c1.url == "new1"
      assert c2.url == "new2"
    end

    @tag :returning
    @tag :with_conflict_target
    test "on conflict ignore and returning" do
      post = %Post{title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:uuid])
      assert inserted.id

      {:ok, not_inserted} = TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:uuid], returning: true)
      assert not_inserted.id == nil
    end

    @tag :without_conflict_target
    test "on conflict query" do
      on_conflict = from Post, update: [set: [title: "second"]]
      post = %Post{title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict)
      assert inserted.id

      {:ok, updated} = TestRepo.insert(post, on_conflict: on_conflict)
      assert updated.id == inserted.id
      assert updated.title != "second"
      assert TestRepo.get!(Post, inserted.id).title == "second"
    end

    @tag :with_conflict_target
    test "on conflict query and conflict target" do
      on_conflict = from Post, update: [set: [title: "second"]]
      post = %Post{title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
      assert inserted.id

      # Error on non-conflict target
      assert catch_error(TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:id]))

      {:ok, updated} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
      assert updated.id == inserted.id
      assert updated.title != "second"
      assert TestRepo.get!(Post, inserted.id).title == "second"
    end

    @tag :without_conflict_target
    test "on conflict replace_all" do
      post = %Post{title: "first", text: "text", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: :replace_all)
      assert inserted.id

      # Error on non-conflict target
      post = %Post{id: inserted.id, title: "updated",
                   text: "updated", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}

      # Error on conflict target
      post = TestRepo.insert!(post, on_conflict: :replace_all)
      assert post.title == "updated"
      assert post.text == "updated"

      assert TestRepo.all(from p in Post, select: p.title) == ["updated"]
      assert TestRepo.all(from p in Post, select: p.text) == ["updated"]
      assert TestRepo.all(from p in Post, select: count(p.id)) == [1]
    end

    @tag :with_conflict_target
    test "on conflict replace_all and conflict target" do
      post = %Post{title: "first", text: "text", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}
      {:ok, inserted} = TestRepo.insert(post, on_conflict: :replace_all, conflict_target: :id)
      assert inserted.id

      # Error on non-conflict target
      post = %Post{id: inserted.id, title: "updated",
                   text: "updated", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"}

      # Error on conflict target
      post = TestRepo.insert!(post, on_conflict: :replace_all, conflict_target: :id)
      assert post.title == "updated"
      assert post.text == "updated"

      assert TestRepo.all(from p in Post, select: p.title) == ["updated"]
      assert TestRepo.all(from p in Post, select: p.text) == ["updated"]
      assert TestRepo.all(from p in Post, select: count(p.id)) == [1]
    end
  end

  describe "upsert via insert_all" do
    @describetag :upsert_all

    test "on conflict raise" do
      post = [title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"]
      {1, nil} = TestRepo.insert_all(Post, [post], on_conflict: :raise)
      assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: :raise))
    end
    @tag :with_conflict_ignore
    test "on conflict ignore" do
      post = [title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"]
      assert TestRepo.insert_all(Post, [post], on_conflict: :nothing) == {1, []}

      # PG returns 0, MySQL returns 1
      {entries, nil} = TestRepo.insert_all(Post, [post], on_conflict: :nothing)
      assert entries == 0 or entries == 1

      assert length(TestRepo.all(Post)) == 1
    end

    @tag :with_conflict_target
    test "on conflict ignore and conflict target" do
      post = [title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"]
      assert TestRepo.insert_all(Post, [post], on_conflict: :nothing, conflict_target: [:uuid]) ==
             {1, []}

      # Error on non-conflict target
      assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: :nothing, conflict_target: [:id]))

      # Error on conflict target
      assert TestRepo.insert_all(Post, [post], on_conflict: :nothing, conflict_target: [:uuid]) ==
             {0, []}
    end

    @tag :with_conflict_target
    test "on conflict keyword list and conflict target" do
      on_conflict = [set: [title: "second"]]
      post = [title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"]
      {1, []} = TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid])

      # Error on non-conflict target
      assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:id]))

      # Error on conflict target
      assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
             {1, []}
      assert TestRepo.all(from p in Post, select: p.title) == ["second"]
    end

    @tag :with_conflict_target
    @tag :returning
    test "on conflict keyword list and conflict target and returning and source field" do
      on_conflict = [set: [url: "new"]]
      permalink = [url: "old"]

      assert {1, [%Permalink{url: "old"}]} =
             TestRepo.insert_all(Permalink, [permalink],
                                 on_conflict: on_conflict, conflict_target: [:url], returning: [:url])

      assert {1, [%Permalink{url: "new"}]} =
             TestRepo.insert_all(Permalink, [permalink],
                                 on_conflict: on_conflict, conflict_target: [:url], returning: [:url])
    end

    @tag :with_conflict_target
    test "on conflict query and conflict target" do
      on_conflict = from Post, update: [set: [title: "second"]]
      post = [title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"]
      assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
             {1, []}

      # Error on non-conflict target
      assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:id]))

      # Error on conflict target
      assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
             {1, []}
      assert TestRepo.all(from p in Post, select: p.title) == ["second"]
    end

    @tag :returning
    @tag :with_conflict_target
    test "on conflict query and conflict target and returning" do
      on_conflict = from Post, update: [set: [title: "second"]]
      post = [title: "first", uuid: "6fa459ea-ee8a-3ca4-894e-db77e160355e"]
      {1, [%{id: id}]} = TestRepo.insert_all(Post, [post], on_conflict: on_conflict,
                                            conflict_target: [:uuid], returning: [:id])

      # Error on non-conflict target
      assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: on_conflict,
                                             conflict_target: [:id], returning: [:id]))

      # Error on conflict target
      {1, [%Post{id: ^id, title: "second"}]} =
        TestRepo.insert_all(Post, [post], on_conflict: on_conflict,
                            conflict_target: [:uuid], returning: [:id, :title])
    end

    @tag :with_conflict_target
    test "source (without an ecto schema) on conflict query and conflict target" do
      on_conflict = [set: [title: "second"]]
      {:ok, uuid} = Tds.UUID.dump("6fa459ea-ee8a-3ca4-894e-db77e160355e")
      post = [title: "first", uuid: uuid]
      assert TestRepo.insert_all("posts", [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
             {1, []}

      # Error on non-conflict target
      assert catch_error(TestRepo.insert_all("posts", [post], on_conflict: on_conflict, conflict_target: [:id]))

      # Error on conflict target
      assert TestRepo.insert_all("posts", [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
             {1, []}
      assert TestRepo.all(from p in Post, select: p.title) == ["second"]
    end

    @tag :without_conflict_target
    test "on conflict replace_all" do
      post_first = %Post{title: "first", public: true}
      post_second = %Post{title: "second", public: false}

      {:ok, inserted_first} = TestRepo.insert(post_first, on_conflict: :replace_all)
      {:ok, inserted_second} = TestRepo.insert(post_second, on_conflict: :replace_all)

      assert inserted_first.id
      assert inserted_second.id
      assert TestRepo.all(from p in Post, select: count(p.id)) == [2]

      # multiple record change value
      changes = [%{id: inserted_first.id, title: "first_updated", text: "first_updated"},
                 %{id: inserted_second.id, title: "second_updated", text: "second_updated"}]

      TestRepo.insert_all(Post, changes, on_conflict: :replace_all)

      assert TestRepo.all(from p in Post, select: count(p.id)) == [2]

      updated_first =  TestRepo.get(Post, inserted_first.id)
      assert updated_first.title == "first_updated"
      assert updated_first.text == "first_updated"

      updated_first =  TestRepo.get(Post, inserted_second.id)
      assert updated_first.title == "second_updated"
      assert updated_first.text == "second_updated"
    end

    @tag :with_conflict_target
    test "on conflict replace_all and conflict_target" do
      post_first = %Post{title: "first", public: true}
      post_second = %Post{title: "second", public: false}

      {:ok, inserted_first} = TestRepo.insert(post_first, on_conflict: :replace_all, conflict_target: :id)
      {:ok, inserted_second} = TestRepo.insert(post_second, on_conflict: :replace_all, conflict_target: :id)

      assert inserted_first.id
      assert inserted_second.id
      assert TestRepo.all(from p in Post, select: count(p.id)) == [2]

      # multiple record change value
      changes = [%{id: inserted_first.id, title: "first_updated", text: "first_updated"},
                 %{id: inserted_second.id, title: "second_updated", text: "second_updated"}]

      TestRepo.insert_all(Post, changes, on_conflict: :replace_all, conflict_target: :id)

      assert TestRepo.all(from p in Post, select: count(p.id)) == [2]

      updated_first =  TestRepo.get(Post, inserted_first.id)
      assert updated_first.title == "first_updated"
      assert updated_first.text == "first_updated"


      updated_first =  TestRepo.get(Post, inserted_second.id)
      assert updated_first.title == "second_updated"
      assert updated_first.text == "second_updated"
    end
  end
end
