defmodule Ecto.Integration.RepoTest do use Ecto.Integration.Case, async: Application.compile_env(:ecto, :async_integration_tests, true) alias Ecto.Integration.TestRepo import Ecto.Query alias Ecto.Integration.Post alias Ecto.Integration.Order 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.PostUserCompositePk test "returns already started for started repos" do assert {:error, {:already_started, _}} = TestRepo.start_link() end test "supports unnamed repos" do assert {:ok, pid} = TestRepo.start_link(name: nil) assert Ecto.Repo.Queryable.all(pid, Post, Ecto.Repo.Supervisor.tuplet(pid, [])) == [] end test "all empty" do assert TestRepo.all(Post) == [] assert TestRepo.all(from p in Post) == [] end test "all 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: p.title not 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 "all using named from" do TestRepo.insert!(%Post{title: "hello"}) query = from(p in Post, as: :post) |> where([post: p], p.title == "hello") assert [_] = TestRepo.all query end test "all 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 test "all shares metadata" do TestRepo.insert!(%Post{title: "title1"}) TestRepo.insert!(%Post{title: "title2"}) [post1, post2] = TestRepo.all(Post) assert :erts_debug.same(post1.__meta__, post2.__meta__) [new_post1, new_post2] = TestRepo.all(Post) assert :erts_debug.same(post1.__meta__, new_post1.__meta__) assert :erts_debug.same(post2.__meta__, new_post2.__meta__) end @tag :invalid_prefix test "all 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", visits: 1} meta = post.__meta__ assert %Post{} = inserted = TestRepo.insert!(post) assert %Post{} = updated = TestRepo.update!(Ecto.Changeset.change(inserted, visits: 2)) 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)a) 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"}) 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")) # Check we can still insert the post after the invalid prefix attempt assert %Post{id: _} = TestRepo.insert!(%Post{}) end test "insert and update with changeset" do # On insert we merge the fields and changes changeset = Ecto.Changeset.cast(%Post{visits: 13, title: "wrong"}, %{"title" => "hello", "temp" => "unknown"}, ~w(title temp)a) post = TestRepo.insert!(changeset) assert %Post{visits: 13, title: "hello", temp: "unknown"} = post assert %Post{visits: 13, 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 | visits: 17}, %{"title" => "world", "temp" => "unknown"}, ~w(title temp)a) assert %Post{visits: 17, title: "world", temp: "unknown"} = TestRepo.update!(changeset) assert %Post{visits: 13, 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)a) 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, CustomPermalink, 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)a) assert %Post{id: 13} = post = TestRepo.insert!(changeset) changeset = Ecto.Changeset.cast(post, %{"id" => "15"}, ~w(id)a) assert %Post{id: 15} = TestRepo.update!(changeset) end test "insert and fetch a schema with utc timestamps" do datetime = DateTime.from_unix!(System.os_time(:second), :second) 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_comment = TestRepo.insert!(%Comment{}) changeset_ok = base_comment |> cast(%{"text" => "foo.bar"}, ~w(text)a) |> optimistic_lock(:lock_version) TestRepo.update!(changeset_ok) changeset_stale = base_comment |> cast(%{"text" => "foo.bat"}, ~w(text)a) |> optimistic_lock(:lock_version) assert_raise Ecto.StaleEntryError, fn -> TestRepo.update!(changeset_stale) end assert_raise Ecto.StaleEntryError, fn -> TestRepo.delete!(changeset_stale) end end test "optimistic locking in update operation with nil field" do import Ecto.Changeset, only: [cast: 3, optimistic_lock: 3] base_comment = %Comment{} |> cast(%{lock_version: nil}, [:lock_version]) |> TestRepo.insert!() incrementer = fn nil -> 1 old_value -> old_value + 1 end changeset_ok = base_comment |> cast(%{"text" => "foo.bar"}, ~w(text)a) |> optimistic_lock(:lock_version, incrementer) updated = TestRepo.update!(changeset_ok) assert updated.text == "foo.bar" assert updated.lock_version == 1 end test "optimistic locking in delete operation with nil field" do import Ecto.Changeset, only: [cast: 3, optimistic_lock: 3] base_comment = %Comment{} |> cast(%{lock_version: nil}, [:lock_version]) |> TestRepo.insert!() incrementer = fn nil -> 1 old_value -> old_value + 1 end changeset_ok = optimistic_lock(base_comment, :lock_version, incrementer) TestRepo.delete!(changeset_ok) refute TestRepo.get(Comment, base_comment.id) end @tag :unique_constraint test "unique constraint" do changeset = Ecto.Changeset.change(%Post{}, uuid: Ecto.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 =~ "\"posts_uuid_index\" (unique_constraint)" assert exception.message =~ "The changeset has not defined any constraint." assert exception.message =~ "call `unique_constraint/3`" 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 =~ "\"posts_email_changeset\" (unique_constraint)" {:error, changeset} = changeset |> Ecto.Changeset.unique_constraint(:uuid) |> TestRepo.insert() assert changeset.errors == [uuid: {"has already been taken", [constraint: :unique, constraint_name: "posts_uuid_index"]}] assert changeset.data.__meta__.state == :built end @tag :unique_constraint test "unique constraint from association" do uuid = Ecto.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.(Ecto.UUID.generate())] } [_, p2, _] = changeset.changes.posts assert p2.errors == [uuid: {"has already been taken", [constraint: :unique, constraint_name: "posts_uuid_index"]}] end @tag :id_type @tag :unique_constraint test "unique constraint with binary_id" do changeset = Ecto.Changeset.change(%Custom{}, uuid: Ecto.UUID.generate()) {:ok, _} = TestRepo.insert(changeset) {:error, changeset} = changeset |> Ecto.Changeset.unique_constraint(:uuid) |> TestRepo.insert() assert changeset.errors == [uuid: {"has already been taken", [constraint: :unique, constraint_name: "customs_uuid_index"]}] 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 =~ "\"comments_post_id_fkey\" (foreign_key_constraint)" assert exception.message =~ "The changeset has not defined any constraint." assert exception.message =~ "call `foreign_key_constraint/3`" 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 =~ "\"comments_post_id_other\" (foreign_key_constraint)" {:error, changeset} = changeset |> Ecto.Changeset.foreign_key_constraint(:post_id) |> TestRepo.insert() assert changeset.errors == [post_id: {"does not exist", [constraint: :foreign, constraint_name: "comments_post_id_fkey"]}] 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 =~ "\"comments_post_id_fkey\" (foreign_key_constraint)" 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 =~ "\"comments_post_id_other\" (foreign_key_constraint)" {:error, changeset} = changeset |> Ecto.Changeset.assoc_constraint(:post) |> TestRepo.insert() assert changeset.errors == [post: {"does not exist", [constraint: :assoc, constraint_name: "comments_post_id_fkey"]}] 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 =~ "\"permalinks_user_id_fkey\" (foreign_key_constraint)" 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 =~ "\"permalinks_user_id_pther\" (foreign_key_constraint)" 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", [constraint: :no_assoc, constraint_name: "permalinks_user_id_fkey"]}] end @tag :foreign_key_constraint test "insert and update with embeds during failing child foreign key" do changeset = Order |> struct(%{}) |> order_changeset(%{item: %{price: 10}, permalink: %{post_id: 0}}) {:error, changeset} = TestRepo.insert(changeset) assert %Ecto.Changeset{} = changeset.changes.item order = Order |> struct(%{}) |> order_changeset(%{}) |> TestRepo.insert!() |> TestRepo.preload([:permalink]) changeset = order_changeset(order, %{item: %{price: 10}, permalink: %{post_id: 0}}) assert %Ecto.Changeset{} = changeset.changes.item {:error, changeset} = TestRepo.update(changeset) assert %Ecto.Changeset{} = changeset.changes.item end def order_changeset(order, params) do order |> Ecto.Changeset.cast(params, [:permalink_id]) |> Ecto.Changeset.cast_embed(:item, with: &item_changeset/2) |> Ecto.Changeset.cast_assoc(:permalink, with: &permalink_changeset/2) end def item_changeset(item, params) do item |> Ecto.Changeset.cast(params, [:price]) end def permalink_changeset(comment, params) do comment |> Ecto.Changeset.cast(params, [:post_id]) |> Ecto.Changeset.assoc_constraint(:post) end test "unsafe_validate_unique/4" do {:ok, inserted_post} = TestRepo.insert(%Post{title: "Greetings", visits: 13}) new_post_changeset = Post.changeset(%Post{}, %{title: "Greetings", visits: 17}) 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, %{visits: 17}) changeset = Ecto.Changeset.unsafe_validate_unique(update_changeset, [:title], TestRepo) assert changeset.errors[:title] == nil # cannot conflict with itself end test "unsafe_validate_unique/4 with composite keys" do {:ok, inserted_post} = TestRepo.insert(%CompositePk{a: 123, b: 456, name: "UniqueName"}) different_pk = CompositePk.changeset(%CompositePk{}, %{name: "UniqueName", a: 789, b: 321}) changeset = Ecto.Changeset.unsafe_validate_unique(different_pk, [:name], TestRepo) assert changeset.errors[:name] == {"has already been taken", validation: :unsafe_unique, fields: [:name]} partial_pk = CompositePk.changeset(%CompositePk{}, %{name: "UniqueName", a: 789, b: 456}) changeset = Ecto.Changeset.unsafe_validate_unique(partial_pk, [:name], TestRepo) assert changeset.errors[:name] == {"has already been taken", validation: :unsafe_unique, fields: [:name]} update_changeset = CompositePk.changeset(inserted_post, %{name: "NewName"}) changeset = Ecto.Changeset.unsafe_validate_unique(update_changeset, [:name], TestRepo) assert changeset.valid? assert changeset.errors[:name] == nil # cannot conflict with itself end test "get(!)" do post1 = TestRepo.insert!(%Post{title: "1"}) post2 = TestRepo.insert!(%Post{title: "2"}) 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 TestRepo.get(Post, post1.id) == nil 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: "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", visits: 1}) post2 = TestRepo.insert!(%Post{title: "2", visits: 2}) assert post1 == TestRepo.get_by(Post, id: post1.id) assert post1 == TestRepo.get_by(Post, title: post1.title) assert post1 == TestRepo.get_by(Post, id: post1.id, title: post1.title) assert post2 == TestRepo.get_by(Post, id: to_string(post2.id)) # With casting assert nil == TestRepo.get_by(Post, title: "hey") assert nil == TestRepo.get_by(Post, id: post2.id, visits: 3) assert post1 == TestRepo.get_by!(Post, id: post1.id) assert post1 == TestRepo.get_by!(Post, title: post1.title) assert post1 == TestRepo.get_by!(Post, id: post1.id, visits: 1) 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, title: "hey") end end test "reload" do post1 = TestRepo.insert!(%Post{title: "1", visits: 1}) post2 = TestRepo.insert!(%Post{title: "2", visits: 2}) assert post1 == TestRepo.reload(post1) assert [post1, post2] == TestRepo.reload([post1, post2]) assert [post1, post2, nil] == TestRepo.reload([post1, post2, %Post{id: 0}]) assert nil == TestRepo.reload(%Post{id: 0}) # keeps order as received in the params assert [post2, post1] == TestRepo.reload([post2, post1]) TestRepo.update_all(Post, inc: [visits: 1]) assert [%{visits: 2}, %{visits: 3}] = TestRepo.reload([post1, post2]) end test "reload ignores preloads" do post = TestRepo.insert!(%Post{title: "1", visits: 1}) |> TestRepo.preload(:comments) assert %{comments: %Ecto.Association.NotLoaded{}} = TestRepo.reload(post) end test "reload!" do post1 = TestRepo.insert!(%Post{title: "1", visits: 1}) post2 = TestRepo.insert!(%Post{title: "2", visits: 2}) assert post1 == TestRepo.reload!(post1) assert [post1, post2] == TestRepo.reload!([post1, post2]) assert_raise RuntimeError, ~r"could not reload", fn -> TestRepo.reload!([post1, post2, %Post{id: -1}]) end assert_raise Ecto.NoResultsError, fn -> TestRepo.reload!(%Post{id: -1}) end assert [post2, post1] == TestRepo.reload([post2, post1]) TestRepo.update_all(Post, inc: [visits: 1]) assert [%{visits: 2}, %{visits: 3}] = TestRepo.reload!([post1, post2]) end test "first, last and one(!)" do post1 = TestRepo.insert!(%Post{title: "1"}) post2 = TestRepo.insert!(%Post{title: "2"}) 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 |> last |> TestRepo.one assert_raise Ecto.NoResultsError, fn -> query |> first |> TestRepo.one! end assert_raise Ecto.NoResultsError, fn -> query |> last |> TestRepo.one! end end test "exists?" do TestRepo.insert!(%Post{title: "1", visits: 2}) TestRepo.insert!(%Post{title: "2", visits: 1}) query = from p in Post, where: not is_nil(p.title), limit: 2 assert query |> TestRepo.exists? == true query = from p in Post, where: p.title == "1", select: p.title assert query |> TestRepo.exists? == true query = from p in Post, where: is_nil(p.id) assert query |> TestRepo.exists? == false query = from p in Post, where: is_nil(p.id) assert query |> TestRepo.exists? == false query = from(p in Post, select: {p.visits, avg(p.visits)}, group_by: p.visits, having: avg(p.visits) > 1) assert query |> TestRepo.exists? == true 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)) # 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 end @tag :decimal_precision test "aggregate avg" do TestRepo.insert!(%Post{visits: 10}) TestRepo.insert!(%Post{visits: 12}) TestRepo.insert!(%Post{visits: 14}) TestRepo.insert!(%Post{visits: 14}) assert "12.5" <> _ = to_string(TestRepo.aggregate(Post, :avg, :visits)) end @tag :inline_order_by test "aggregate with distinct" do TestRepo.insert!(%Post{visits: 10}) TestRepo.insert!(%Post{visits: 12}) TestRepo.insert!(%Post{visits: 14}) TestRepo.insert!(%Post{visits: 14}) query = from Post, order_by: [asc: :visits], distinct: true assert TestRepo.aggregate(query, :count, :visits) == 3 end @tag :insert_cell_wise_defaults 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, nil} = TestRepo.insert_all(Post, [[], []]) assert [%Post{}, %Post{}] = TestRepo.all(Post) assert {0, nil} = TestRepo.insert_all("posts", []) assert {0, nil} = TestRepo.insert_all({"posts", Post}, []) end @tag :insert_select test "insert all with query for single fields" do comment = TestRepo.insert!(%Comment{text: "1", lock_version: 1}) text_query = from(c in Comment, select: c.text, where: [id: ^comment.id, lock_version: 1]) lock_version_query = from(c in Comment, select: c.lock_version, where: [id: ^comment.id]) rows = [ [text: "2", lock_version: lock_version_query], [lock_version: lock_version_query, text: "3"], [text: text_query], [text: text_query, lock_version: lock_version_query], [lock_version: 6, text: "6"] ] assert {5, nil} = TestRepo.insert_all(Comment, rows, []) inserted_rows = Comment |> where([c], c.id != ^comment.id) |> TestRepo.all() assert [%Comment{text: "2", lock_version: 1}, %Comment{text: "3", lock_version: 1}, %Comment{text: "1"}, %Comment{text: "1", lock_version: 1}, %Comment{text: "6", lock_version: 6}] = inserted_rows end describe "insert_all with source query" do @tag :upsert_all @tag :with_conflict_target @tag :concat test "insert_all with query and conflict target" do {:ok, %Post{id: id}} = TestRepo.insert(%Post{ title: "A generic title" }) source = from p in Post, select: %{ title: fragment("concat(?, ?, ?)", p.title, type(^" suffix ", :string), p.id) } assert {1, _} = TestRepo.insert_all(Post, source, conflict_target: [:id], on_conflict: :replace_all) expected_id = id + 1 expected_title = "A generic title suffix #{id}" assert %Post{title: ^expected_title} = TestRepo.get(Post, expected_id) end @tag :returning @tag :concat test "insert_all with query and returning" do {:ok, %Post{id: id}} = TestRepo.insert(%Post{ title: "A generic title" }) source = from p in Post, select: %{ title: fragment("concat(?, ?, ?)", p.title, type(^" suffix ", :string), p.id) } assert {1, returns} = TestRepo.insert_all(Post, source, returning: [:id, :title]) expected_id = id + 1 expected_title = "A generic title suffix #{id}" assert [%Post{id: ^expected_id, title: ^expected_title}] = returns end @tag :upsert_all @tag :without_conflict_target @tag :concat test "insert_all with query and on_conflict" do {:ok, %Post{id: id}} = TestRepo.insert(%Post{ title: "A generic title" }) source = from p in Post, select: %{ title: fragment("concat(?, ?, ?)", p.title, type(^" suffix ", :string), p.id) } assert {1, _} = TestRepo.insert_all(Post, source, on_conflict: :replace_all) expected_id = id + 1 expected_title = "A generic title suffix #{id}" assert %Post{title: ^expected_title} = TestRepo.get(Post, expected_id) end @tag :concat test "insert_all with query" do {:ok, %Post{id: id}} = TestRepo.insert(%Post{ title: "A generic title" }) source = from p in Post, select: %{ title: fragment("concat(?, ?, ?)", p.title, type(^" suffix ", :string), p.id) } assert {1, _} = TestRepo.insert_all(Post, source) expected_id = id + 1 expected_title = "A generic title suffix #{id}" assert %Post{title: ^expected_title} = TestRepo.get(Post, expected_id) end end @tag :invalid_prefix @tag :insert_cell_wise_defaults test "insert all with invalid prefix" do assert catch_error(TestRepo.insert_all(Post, [[], []], prefix: "oops")) end @tag :returning @tag :insert_cell_wise_defaults 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 @tag :insert_cell_wise_defaults 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 @tag :insert_cell_wise_defaults 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 @tag :insert_cell_wise_defaults test "insert all with dumping" do uuid = Ecto.UUID.generate() assert {1, nil} = TestRepo.insert_all(Post, [%{uuid: uuid}]) assert [%Post{uuid: ^uuid, title: nil}] = TestRepo.all(Post) end @tag :insert_cell_wise_defaults 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 = Ecto.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 describe "placeholders" do @describetag :placeholders test "Repo.insert_all fills in placeholders" do placeholders = %{foo: 100, bar: "test"} bar_ph = {:placeholder, :bar} foo_ph = {:placeholder, :foo} entries = [ %{intensity: 1.0, title: bar_ph, posted: ~D[2020-12-21], visits: foo_ph}, %{intensity: 2.0, title: bar_ph, posted: ~D[2000-12-21], visits: foo_ph} ] |> Enum.map(&Map.put(&1, :uuid, Ecto.UUID.generate)) TestRepo.insert_all(Post, entries, placeholders: placeholders) query = from(p in Post, select: {p.intensity, p.title, p.visits}) assert [{1.0, "test", 100}, {2.0, "test", 100}] == TestRepo.all(query) end test "Repo.insert_all accepts non-atom placeholder keys" do placeholders = %{10 => "integer key", {:foo, :bar} => "tuple key"} entries = [%{text: {:placeholder, 10}}, %{text: {:placeholder, {:foo, :bar}}}] TestRepo.insert_all(Comment, entries, placeholders: placeholders) query = from(c in Comment, select: c.text) assert ["integer key", "tuple key"] == TestRepo.all(query) end test "Repo.insert_all fills in placeholders with keyword list entries" do TestRepo.insert_all(Barebone, [[num: {:placeholder, :foo}]], placeholders: %{foo: 100}) query = from(b in Barebone, select: b.num) assert [100] == TestRepo.all(query) end @tag :upsert_all @tag :with_conflict_target test "Repo.insert_all upserts and fills in placeholders with conditioned on_conflict query" do do_not_update_title = "don't touch me" posted_value = from p in Post, where: p.public == ^true and p.id > ^0, select: p.posted, limit: 1 on_conflict = from p in Post, update: [set: [title: "updated"]], where: p.title != ^do_not_update_title placeholders = %{visits: 1, title: "title"} post1 = [ visits: {:placeholder, :visits}, title: {:placeholder, :title}, uuid: Ecto.UUID.generate(), posted: posted_value ] post2 = [ title: do_not_update_title, uuid: Ecto.UUID.generate(), posted: posted_value ] assert TestRepo.insert_all(Post, [post1, post2], placeholders: placeholders, on_conflict: on_conflict, conflict_target: [:uuid] ) == {2, nil} # only update first post assert TestRepo.insert_all(Post, [post1, post2], placeholders: placeholders, on_conflict: on_conflict, conflict_target: [:uuid] ) == {1, nil} assert TestRepo.aggregate(where(Post, title: "updated"), :count) == 1 end end test "update all" do assert post1 = TestRepo.insert!(%Post{title: "1"}) assert post2 = TestRepo.insert!(%Post{title: "2"}) assert post3 = TestRepo.insert!(%Post{title: "3"}) assert {3, nil} = TestRepo.update_all(Post, set: [title: "x"]) assert %Post{title: "x"} = TestRepo.reload(post1) assert %Post{title: "x"} = TestRepo.reload(post2) assert %Post{title: "x"} = TestRepo.reload(post3) assert {3, nil} = TestRepo.update_all("posts", [set: [title: nil]]) assert %Post{title: nil} = TestRepo.reload(post1) assert %Post{title: nil} = TestRepo.reload(post2) assert %Post{title: nil} = TestRepo.reload(post3) 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(select(Post, [p], p), [set: [title: "x"]]) [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(select(Post, [:id, :visits]), [set: [visits: 11]]) [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(select("posts", [:id, :title]), [set: [title: "x"]]) [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: [visits: ^17]]) assert {2, nil} = TestRepo.update_all(query, set: [title: "x"]) assert %Post{title: "x", visits: 17} = TestRepo.get(Post, id1) assert %Post{title: "x", visits: 17} = TestRepo.get(Post, id2) assert %Post{title: "3", visits: 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, nil} = 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, nil} = 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, nil} = 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, nil} = 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 visits = 13 datetime = ~N[2014-01-16 20:26:51] assert %Post{id: id} = TestRepo.insert!(%Post{}) assert {1, nil} = TestRepo.update_all(Post, set: [visits: visits, inserted_at: datetime]) assert %Post{visits: 13, inserted_at: ^datetime} = TestRepo.get(Post, id) end test "delete all" do assert %Post{} = TestRepo.insert!(%Post{title: "1"}) assert %Post{} = TestRepo.insert!(%Post{title: "2"}) assert %Post{} = TestRepo.insert!(%Post{title: "3"}) assert {3, nil} = TestRepo.delete_all(Post) 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"}) assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"}) assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"}) assert {3, posts} = TestRepo.delete_all(select(Post, [p], p)) [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"}) assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"}) assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"}) assert {3, posts} = TestRepo.delete_all(select("posts", [: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"}) assert %Post{} = TestRepo.insert!(%Post{title: "2"}) assert %Post{} = TestRepo.insert!(%Post{title: "3"}) query = from(p in Post, where: p.title == "1" or p.title == "2") assert {2, nil} = 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"}) 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, nil} = 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"}) 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", visits: 13}) assert [{"1", 13}] == TestRepo.all(from p in Post, select: {p.title, p.visits}) assert [["1", 13]] == TestRepo.all(from p in Post, select: [p.title, p.visits]) assert [%{:title => "1", 3 => 13, "visits" => 13}] == TestRepo.all(from p in Post, select: %{ :title => p.title, "visits" => p.visits, 3 => p.visits }) assert [%{:title => "1", "1" => 13, "visits" => 13}] == TestRepo.all(from p in Post, select: %{ :title => p.title, p.title => p.visits, "visits" => p.visits }) 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", visits: 13}) assert [%Post{:title => "new title", visits: 13}] = TestRepo.all(from p in Post, select: %{p | title: "new title"}) assert [%Post{title: "new title", visits: 13}] = 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 "map update on association" do p = TestRepo.insert!(%Post{}) TestRepo.insert!(%Comment{post_id: p.id, text: "comment text"}) TestRepo.insert!(%Comment{}) query = from(c in Comment, left_join: p in Post, on: c.post_id == p.id, select: %{p | temp: c.text}) assert [%Post{:temp => "comment text"}, nil] = TestRepo.all(query) 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 "take with join assocs and single nil column" do %{id: post_id} = TestRepo.insert!(%Post{title: "1"}, counter: nil) TestRepo.insert!(%Comment{post_id: post_id, text: "comment"}) assert %{counter: nil} == TestRepo.one(from p in Post, join: c in assoc(p, :comments), where: p.title == "1", select: map(p, [: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 date = Date.utc_today() %Post{id: post_id} = TestRepo.insert!(%Post{title: "1", counter: nil, posted: date, public: false}) # 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}, %{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 on outer join with map %Permalink{} = TestRepo.insert!(%Permalink{post_id: post_id, url: "Q", title: "Z"}) # left join record is present assert [%{url: "Q", title: "1", posted: _date}] = Permalink |> join(:left, [l], p in Post, on: l.post_id == p.id) |> select([l, p], merge(l, map(p, ^~w(title posted)a))) |> TestRepo.all() assert [%{url: "Q", title: "1", posted: _date}] = Permalink |> join(:left, [l], p in Post, on: l.post_id == p.id) |> select_merge([_l, p], map(p, ^~w(title posted)a)) |> TestRepo.all() # left join record is not present assert [%{url: "Q", title: "Z", posted: nil}] = Permalink |> join(:left, [l], p in Post, on: l.post_id == p.id and p.public == true) |> select([l, p], merge(l, map(p, ^~w(title posted)a))) |> TestRepo.all() assert [%{url: "Q", title: "Z", posted: nil}] = Permalink |> join(:left, [l], p in Post, on: l.post_id == p.id and p.public == true) |> select_merge([_l, p], map(p, ^~w(title posted)a)) |> TestRepo.all() end test "merge with update on self" do %Post{} = TestRepo.insert!(%Post{title: "1", counter: 1}) assert [%Post{title: "1", counter: 2}] = Post |> select([p], merge(p, %{p | counter: 2})) |> TestRepo.all() assert [%Post{title: "1", counter: 2}] = Post |> select([p], p) |> select_merge([p], %{p | counter: 2}) |> TestRepo.all() end test "merge within subquery" do %Post{} = TestRepo.insert!(%Post{title: "1", counter: 1}) subquery = Post |> select_merge([p], %{p | counter: 2}) |> subquery() assert [%Post{title: "1", counter: 2}] = TestRepo.all(subquery) end @tag :selected_as_with_group_by test "selected_as/2 with group_by" do TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 3}) TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 2}) TestRepo.insert!(%Post{posted: ~D[2020-12-20], visits: nil}) query = from p in Post, select: %{ posted: selected_as(p.posted, :date), min_visits: p.visits |> coalesce(0) |> min() }, group_by: selected_as(:date), order_by: p.posted assert [%{posted: ~D[2020-12-20], min_visits: 0}, %{posted: ~D[2020-12-21], min_visits: 2}] = TestRepo.all(query) end @tag :selected_as_with_order_by test "selected_as/2 with order_by" do TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 3}) TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 2}) TestRepo.insert!(%Post{posted: ~D[2020-12-20], visits: nil}) base_query = from p in Post, select: %{ posted: p.posted, min_visits: p.visits |> coalesce(0) |> min() |> selected_as(:min_visits) }, group_by: p.posted # ascending order results = base_query |> order_by(selected_as(:min_visits)) |> TestRepo.all() assert [%{posted: ~D[2020-12-20], min_visits: 0}, %{posted: ~D[2020-12-21], min_visits: 2}] = results # descending order results = base_query |> order_by([desc: selected_as(:min_visits)]) |> TestRepo.all() assert [%{posted: ~D[2020-12-21], min_visits: 2}, %{posted: ~D[2020-12-20], min_visits: 0}] = results end @tag :selected_as_with_order_by test "selected_as/2 respects custom types" do TestRepo.insert!(%Post{title: "title1", visits: 1}) TestRepo.insert!(%Post{title: "title2"}) uuid = Ecto.UUID.generate() query = from p in Post, select: %{ uuid: type(^uuid, Ecto.UUID) |> selected_as(:uuid), visits: p.visits |> coalesce(0) |> selected_as(:visits) }, order_by: [selected_as(:uuid), selected_as(:visits)] assert [%{uuid: ^uuid, visits: 0}, %{uuid: ^uuid, visits: 1}] = TestRepo.all(query) end @tag :selected_as_with_order_by_expression test "selected_as/2 with order_by expression" do TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 3, intensity: 2.0}) TestRepo.insert!(%Post{posted: ~D[2020-12-20], visits: nil, intensity: 10.0}) results = from(p in Post, select: %{ posted: p.posted, visits: p.visits |> coalesce(0) |> selected_as(:num_visits), intensity: selected_as(p.intensity, :strength) }, order_by: [desc: (selected_as(:num_visits) + selected_as(:strength))] ) |> TestRepo.all() assert [%{posted: ~D[2020-12-20], visits: 0}, %{posted: ~D[2020-12-21], visits: 3}] = results end @tag :selected_as_with_having test "selected_as/2 with having" do TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 3}) TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 2}) TestRepo.insert!(%Post{posted: ~D[2020-12-20], visits: nil}) results = from(p in Post, select: %{ posted: p.posted, min_visits: p.visits |> coalesce(0) |> min() |> selected_as(:min_visits) }, group_by: p.posted, having: selected_as(:min_visits) > 0, or_having: not(selected_as(:min_visits) > 0), order_by: p.posted ) |> TestRepo.all() assert [%{posted: ~D[2020-12-20], min_visits: 0}, %{posted: ~D[2020-12-21], min_visits: 2}] = results end end @tag :distinct_count 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{title: "hello"}) post2 = TestRepo.insert!(%Post{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{title: "goodbye", uuid: nil}) params3 = [title: "goodbye", uuid: post3.uuid] assert [post3] == (from Post, where: ^params3) |> TestRepo.all 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: Ecto.UUID.generate()} {: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: Ecto.UUID.generate(), 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: Ecto.UUID.generate()} {: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: Ecto.UUID.generate()} {: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: Ecto.UUID.generate()} {: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 :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: Ecto.UUID.generate()} {: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: Ecto.UUID.generate()} {: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: Ecto.UUID.generate()} {: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 :with_conflict_target test "on conflict query having condition" do post = %Post{title: "first", counter: 1, uuid: Ecto.UUID.generate()} {:ok, inserted} = TestRepo.insert(post) on_conflict = from Post, where: [counter: 2], update: [set: [title: "second"]] insert_options = [ on_conflict: on_conflict, conflict_target: [:uuid], stale_error_field: :counter ] assert {:error, changeset} = TestRepo.insert(post, insert_options) assert changeset.errors == [counter: {"is stale", [stale: true]}] assert TestRepo.get!(Post, inserted.id).title == "first" end @tag :without_conflict_target test "on conflict replace_all" do post = %Post{title: "first", visits: 13, uuid: Ecto.UUID.generate()} {:ok, inserted} = TestRepo.insert(post, on_conflict: :replace_all) assert inserted.id post = %Post{title: "updated", visits: 17, uuid: post.uuid} post = TestRepo.insert!(post, on_conflict: :replace_all) assert post.id != inserted.id assert post.title == "updated" assert post.visits == 17 assert TestRepo.all(from p in Post, select: {p.id, p.title, p.visits}) == [{post.id, "updated", 17}] 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", visits: 13, uuid: Ecto.UUID.generate()} {:ok, inserted} = TestRepo.insert(post, on_conflict: :replace_all, conflict_target: :uuid) assert inserted.id post = %Post{title: "updated", visits: 17, uuid: post.uuid} post = TestRepo.insert!(post, on_conflict: :replace_all, conflict_target: :uuid) assert post.id != inserted.id assert post.title == "updated" assert post.visits == 17 assert TestRepo.all(from p in Post, select: {p.id, p.title, p.visits}) == [{post.id, "updated", 17}] 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: Ecto.UUID.generate()] {1, nil} = TestRepo.insert_all(Post, [post], on_conflict: :raise) assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: :raise)) end test "on conflict ignore" do post = [title: "first", uuid: Ecto.UUID.generate()] assert TestRepo.insert_all(Post, [post], on_conflict: :nothing) == {1, nil} # 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: Ecto.UUID.generate()] assert TestRepo.insert_all(Post, [post], on_conflict: :nothing, conflict_target: [:uuid]) == {1, nil} # 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, nil} end @tag :with_conflict_target test "on conflict keyword list and conflict target" do on_conflict = [set: [title: "second"]] post = [title: "first", uuid: Ecto.UUID.generate()] {1, nil} = 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, nil} 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 p in Post, where: p.id > ^0, update: [set: [title: "second"]] post = [title: "first", uuid: Ecto.UUID.generate()] assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) == {1, nil} # 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, nil} assert TestRepo.all(from p in Post, select: p.title) == ["second"] end @tag :insert_select @tag :with_conflict_target test "on conflict query and insert select and conflict target" do on_conflict = from p in Post, where: p.id > ^0, update: [set: [title: "second"]] visits_value = from p in Post, where: p.public == ^true and p.id > ^0, select: p.visits, limit: 1 post = [title: "first", uuid: Ecto.UUID.generate(), visits: visits_value] assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) == {1, nil} # 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, nil} 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: Ecto.UUID.generate()] {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} = Ecto.UUID.dump(Ecto.UUID.generate()) post = [title: "first", uuid: uuid] assert TestRepo.insert_all("posts", [post], on_conflict: on_conflict, conflict_target: [:uuid]) == {1, nil} # 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, nil} 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, uuid: Ecto.UUID.generate()} post_second = %Post{title: "second", public: false, uuid: Ecto.UUID.generate()} {:ok, post_first} = TestRepo.insert(post_first, on_conflict: :replace_all) {:ok, post_second} = TestRepo.insert(post_second, on_conflict: :replace_all) assert post_first.id assert post_second.id assert TestRepo.all(from p in Post, select: count(p.id)) == [2] # Multiple record change value: note IDS are also replaced changes = [%{id: post_first.id + 2, title: "first_updated", visits: 1, uuid: post_first.uuid}, %{id: post_second.id + 2, title: "second_updated", visits: 2, uuid: post_second.uuid}] 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, post_first.id + 2) assert updated_first.title == "first_updated" assert updated_first.visits == 1 updated_second = TestRepo.get(Post, post_second.id + 2) assert updated_second.title == "second_updated" assert updated_second.visits == 2 end @tag :with_conflict_target test "on conflict replace_all and conflict_target" do post_first = %Post{title: "first", public: true, uuid: Ecto.UUID.generate()} post_second = %Post{title: "second", public: false, uuid: Ecto.UUID.generate()} {:ok, post_first} = TestRepo.insert(post_first, on_conflict: :replace_all, conflict_target: :uuid) {:ok, post_second} = TestRepo.insert(post_second, on_conflict: :replace_all, conflict_target: :uuid) assert post_first.id assert post_second.id assert TestRepo.all(from p in Post, select: count(p.id)) == [2] # Multiple record change value: note IDS are also replaced changes = [%{id: post_second.id + 1, title: "first_updated", visits: 1, uuid: post_first.uuid}, %{id: post_second.id + 2, title: "second_updated", visits: 2, uuid: post_second.uuid}] TestRepo.insert_all(Post, changes, on_conflict: :replace_all, conflict_target: :uuid) assert TestRepo.all(from p in Post, select: count(p.id)) == [2] updated_first = TestRepo.get(Post, post_second.id + 1) assert updated_first.title == "first_updated" assert updated_first.visits == 1 updated_second = TestRepo.get(Post, post_second.id + 2) assert updated_second.title == "second_updated" assert updated_second.visits == 2 end @tag :without_conflict_target test "on conflict replace_all_except" do post_first = %Post{title: "first", public: true, uuid: Ecto.UUID.generate()} post_second = %Post{title: "second", public: false, uuid: Ecto.UUID.generate()} {:ok, post_first} = TestRepo.insert(post_first, on_conflict: {:replace_all_except, [:id]}) {:ok, post_second} = TestRepo.insert(post_second, on_conflict: {:replace_all_except, [:id]}) assert post_first.id assert post_second.id assert TestRepo.all(from p in Post, select: count(p.id)) == [2] # Multiple record change value: note IDS are not replaced changes = [%{id: post_first.id + 2, title: "first_updated", visits: 1, uuid: post_first.uuid}, %{id: post_second.id + 2, title: "second_updated", visits: 2, uuid: post_second.uuid}] TestRepo.insert_all(Post, changes, on_conflict: {:replace_all_except, [:id]}) assert TestRepo.all(from p in Post, select: count(p.id)) == [2] updated_first = TestRepo.get(Post, post_first.id) assert updated_first.title == "first_updated" assert updated_first.visits == 1 updated_second = TestRepo.get(Post, post_second.id) assert updated_second.title == "second_updated" assert updated_second.visits == 2 end @tag :with_conflict_target test "on conflict replace_all_except and conflict_target" do post_first = %Post{title: "first", public: true, uuid: Ecto.UUID.generate()} post_second = %Post{title: "second", public: false, uuid: Ecto.UUID.generate()} {:ok, post_first} = TestRepo.insert(post_first, on_conflict: {:replace_all_except, [:id]}, conflict_target: :uuid) {:ok, post_second} = TestRepo.insert(post_second, on_conflict: {:replace_all_except, [:id]}, conflict_target: :uuid) assert post_first.id assert post_second.id assert TestRepo.all(from p in Post, select: count(p.id)) == [2] # Multiple record change value: note IDS are not replaced changes = [%{id: post_first.id + 2, title: "first_updated", visits: 1, uuid: post_first.uuid}, %{id: post_second.id + 2, title: "second_updated", visits: 2, uuid: post_second.uuid}] TestRepo.insert_all(Post, changes, on_conflict: {:replace_all_except, [:id]}, conflict_target: :uuid) assert TestRepo.all(from p in Post, select: count(p.id)) == [2] updated_first = TestRepo.get(Post, post_first.id) assert updated_first.title == "first_updated" assert updated_first.visits == 1 updated_second = TestRepo.get(Post, post_second.id) assert updated_second.title == "second_updated" assert updated_second.visits == 2 end @tag :with_conflict_target test "on conflict replace and conflict_target" do post_first = %Post{title: "first", visits: 10, public: true, uuid: Ecto.UUID.generate()} post_second = %Post{title: "second", visits: 20, public: false, uuid: Ecto.UUID.generate()} {:ok, post_first} = TestRepo.insert(post_first, on_conflict: {:replace, [:title, :visits]}, conflict_target: :uuid) {:ok, post_second} = TestRepo.insert(post_second, on_conflict: {:replace, [:title, :visits]}, conflict_target: :uuid) assert post_first.id assert post_second.id assert TestRepo.all(from p in Post, select: count(p.id)) == [2] # Multiple record change value: note `public` field is not changed changes = [%{id: post_first.id, title: "first_updated", visits: 11, public: false, uuid: post_first.uuid}, %{id: post_second.id, title: "second_updated", visits: 21, public: true, uuid: post_second.uuid}] TestRepo.insert_all(Post, changes, on_conflict: {:replace, [:title, :visits]}, conflict_target: :uuid) assert TestRepo.all(from p in Post, select: count(p.id)) == [2] updated_first = TestRepo.get(Post, post_first.id) assert updated_first.title == "first_updated" assert updated_first.visits == 11 assert updated_first.public == true updated_second = TestRepo.get(Post, post_second.id) assert updated_second.title == "second_updated" assert updated_second.visits == 21 assert updated_second.public == false end end describe "values list" do @describetag :values_list test "all" do uuid_module = uuid_module(TestRepo.__adapter__()) uuid = uuid_module.generate() # Without select values = [%{bid: uuid, visits: 1}, %{bid: uuid, visits: 2}] types = %{bid: uuid_module, visits: :integer} query = from v in values(values, types) assert TestRepo.all(query) == values # With select query = select(query, [v], {v, v.bid}) assert TestRepo.all(query) == Enum.map(values, &{&1, &1.bid}) end test "all with join" do uuid_module = uuid_module(TestRepo.__adapter__()) uuid = uuid_module.generate() values1 = [%{bid: uuid, visits: 1}, %{bid: uuid, visits: 2}] values2 = [%{bid: uuid, visits: 1}] types = %{bid: uuid_module, visits: :integer} query = from v1 in values(values1, types), join: v2 in values(values2, types), on: v1.visits == v2.visits assert TestRepo.all(query) == [%{bid: uuid, visits: 1}] end test "delete_all" do uuid_module = uuid_module(TestRepo.__adapter__()) uuid = uuid_module.generate() _p1 = TestRepo.insert!(%Post{bid: uuid, visits: 1}) p2 = TestRepo.insert!(%Post{bid: uuid, visits: 5}) values = [%{bid: uuid, visits: 1}, %{bid: nil, visits: 1}, %{bid: uuid, visits: 3}] types = %{bid: uuid_module, visits: :integer} query = from p in Post, join: v in values(values, types), on: p.visits == v.visits assert {1, _} = TestRepo.delete_all(query) assert TestRepo.all(Post) == [p2] end test "update_all" do uuid_module = uuid_module(TestRepo.__adapter__()) uuid = uuid_module.generate() TestRepo.insert!(%Post{bid: uuid, visits: 1}) values = [%{bid: uuid, visits: 10}, %{bid: nil, visits: 2}] types = %{bid: uuid_module, visits: :integer} query = from p in Post, join: v in values(values, types), on: p.bid == v.bid, update: [set: [visits: v.visits]] assert {1, _} = TestRepo.update_all(query, []) assert [%{visits: 10}] = TestRepo.all(Post) end defp uuid_module(Ecto.Adapters.Tds), do: Tds.Ecto.UUID defp uuid_module(_), do: Ecto.UUID end end