zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

repo.exs (81580B)


      1 defmodule Ecto.Integration.RepoTest do
      2   use Ecto.Integration.Case, async: Application.compile_env(:ecto, :async_integration_tests, true)
      3 
      4   alias Ecto.Integration.TestRepo
      5   import Ecto.Query
      6 
      7   alias Ecto.Integration.Post
      8   alias Ecto.Integration.Order
      9   alias Ecto.Integration.User
     10   alias Ecto.Integration.Comment
     11   alias Ecto.Integration.Permalink
     12   alias Ecto.Integration.Custom
     13   alias Ecto.Integration.Barebone
     14   alias Ecto.Integration.CompositePk
     15   alias Ecto.Integration.PostUserCompositePk
     16 
     17   test "returns already started for started repos" do
     18     assert {:error, {:already_started, _}} = TestRepo.start_link()
     19   end
     20 
     21   test "supports unnamed repos" do
     22     assert {:ok, pid} = TestRepo.start_link(name: nil)
     23     assert Ecto.Repo.Queryable.all(pid, Post, Ecto.Repo.Supervisor.tuplet(pid, [])) == []
     24   end
     25 
     26   test "all empty" do
     27     assert TestRepo.all(Post) == []
     28     assert TestRepo.all(from p in Post) == []
     29   end
     30 
     31   test "all with in" do
     32     TestRepo.insert!(%Post{title: "hello"})
     33 
     34     # Works without the query cache.
     35     assert_raise Ecto.Query.CastError, fn ->
     36       TestRepo.all(from p in Post, where: p.title in ^nil)
     37     end
     38 
     39     assert [] = TestRepo.all from p in Post, where: p.title in []
     40     assert [] = TestRepo.all from p in Post, where: p.title in ["1", "2", "3"]
     41     assert [] = TestRepo.all from p in Post, where: p.title in ^[]
     42 
     43     assert [_] = TestRepo.all from p in Post, where: p.title not in []
     44     assert [_] = TestRepo.all from p in Post, where: p.title in ["1", "hello", "3"]
     45     assert [_] = TestRepo.all from p in Post, where: p.title in ["1", ^"hello", "3"]
     46     assert [_] = TestRepo.all from p in Post, where: p.title in ^["1", "hello", "3"]
     47 
     48     # Still doesn't work after the query cache.
     49     assert_raise Ecto.Query.CastError, fn ->
     50       TestRepo.all(from p in Post, where: p.title in ^nil)
     51     end
     52   end
     53 
     54   test "all using named from" do
     55     TestRepo.insert!(%Post{title: "hello"})
     56 
     57     query =
     58       from(p in Post, as: :post)
     59       |> where([post: p], p.title == "hello")
     60 
     61     assert [_] = TestRepo.all query
     62   end
     63 
     64   test "all without schema" do
     65     %Post{} = TestRepo.insert!(%Post{title: "title1"})
     66     %Post{} = TestRepo.insert!(%Post{title: "title2"})
     67 
     68     assert ["title1", "title2"] =
     69       TestRepo.all(from(p in "posts", order_by: p.title, select: p.title))
     70 
     71     assert [_] =
     72       TestRepo.all(from(p in "posts", where: p.title == "title1", select: p.id))
     73   end
     74 
     75   test "all shares metadata" do
     76     TestRepo.insert!(%Post{title: "title1"})
     77     TestRepo.insert!(%Post{title: "title2"})
     78 
     79     [post1, post2] = TestRepo.all(Post)
     80     assert :erts_debug.same(post1.__meta__, post2.__meta__)
     81 
     82     [new_post1, new_post2] = TestRepo.all(Post)
     83     assert :erts_debug.same(post1.__meta__, new_post1.__meta__)
     84     assert :erts_debug.same(post2.__meta__, new_post2.__meta__)
     85   end
     86 
     87   @tag :invalid_prefix
     88   test "all with invalid prefix" do
     89     assert catch_error(TestRepo.all("posts", prefix: "oops"))
     90   end
     91 
     92   test "insert, update and delete" do
     93     post = %Post{title: "insert, update, delete", visits: 1}
     94     meta = post.__meta__
     95 
     96     assert %Post{} = inserted = TestRepo.insert!(post)
     97     assert %Post{} = updated = TestRepo.update!(Ecto.Changeset.change(inserted, visits: 2))
     98 
     99     deleted_meta = put_in meta.state, :deleted
    100     assert %Post{__meta__: ^deleted_meta} = TestRepo.delete!(updated)
    101 
    102     loaded_meta = put_in meta.state, :loaded
    103     assert %Post{__meta__: ^loaded_meta} = TestRepo.insert!(post)
    104 
    105     post = TestRepo.one(Post)
    106     assert post.__meta__.state == :loaded
    107     assert post.inserted_at
    108   end
    109 
    110   test "insert, update and delete with field source" do
    111     permalink = %Permalink{url: "url"}
    112     assert %Permalink{url: "url"} = inserted =
    113            TestRepo.insert!(permalink)
    114     assert %Permalink{url: "new"} = updated =
    115            TestRepo.update!(Ecto.Changeset.change(inserted, url: "new"))
    116     assert %Permalink{url: "new"} =
    117            TestRepo.delete!(updated)
    118   end
    119 
    120   @tag :composite_pk
    121   test "insert, update and delete with composite pk" do
    122     c1 = TestRepo.insert!(%CompositePk{a: 1, b: 2, name: "first"})
    123     c2 = TestRepo.insert!(%CompositePk{a: 1, b: 3, name: "second"})
    124 
    125     assert CompositePk |> first |> TestRepo.one == c1
    126     assert CompositePk |> last |> TestRepo.one == c2
    127 
    128     changeset = Ecto.Changeset.cast(c1, %{name: "first change"}, ~w(name)a)
    129     c1 = TestRepo.update!(changeset)
    130     assert TestRepo.get_by!(CompositePk, %{a: 1, b: 2}) == c1
    131 
    132     TestRepo.delete!(c2)
    133     assert TestRepo.all(CompositePk) == [c1]
    134 
    135     assert_raise ArgumentError, ~r"to have exactly one primary key", fn ->
    136       TestRepo.get(CompositePk, [])
    137     end
    138 
    139     assert_raise ArgumentError, ~r"to have exactly one primary key", fn ->
    140       TestRepo.get!(CompositePk, [1, 2])
    141     end
    142   end
    143 
    144   @tag :composite_pk
    145   test "insert, update and delete with associated composite pk" do
    146     user = TestRepo.insert!(%User{})
    147     post = TestRepo.insert!(%Post{title: "post title"})
    148 
    149     user_post = TestRepo.insert!(%PostUserCompositePk{user_id: user.id, post_id: post.id})
    150     assert TestRepo.get_by!(PostUserCompositePk, [user_id: user.id, post_id: post.id]) == user_post
    151     TestRepo.delete!(user_post)
    152     assert TestRepo.all(PostUserCompositePk) == []
    153   end
    154 
    155   @tag :invalid_prefix
    156   test "insert, update and delete with invalid prefix" do
    157     post = TestRepo.insert!(%Post{})
    158     changeset = Ecto.Changeset.change(post, title: "foo")
    159     assert catch_error(TestRepo.insert(%Post{}, prefix: "oops"))
    160     assert catch_error(TestRepo.update(changeset, prefix: "oops"))
    161     assert catch_error(TestRepo.delete(changeset, prefix: "oops"))
    162 
    163     # Check we can still insert the post after the invalid prefix attempt
    164     assert %Post{id: _} = TestRepo.insert!(%Post{})
    165   end
    166 
    167   test "insert and update with changeset" do
    168     # On insert we merge the fields and changes
    169     changeset = Ecto.Changeset.cast(%Post{visits: 13, title: "wrong"},
    170                                     %{"title" => "hello", "temp" => "unknown"}, ~w(title temp)a)
    171 
    172     post = TestRepo.insert!(changeset)
    173     assert %Post{visits: 13, title: "hello", temp: "unknown"} = post
    174     assert %Post{visits: 13, title: "hello", temp: "temp"} = TestRepo.get!(Post, post.id)
    175 
    176     # On update we merge only fields, direct schema changes are discarded
    177     changeset = Ecto.Changeset.cast(%{post | visits: 17},
    178                                     %{"title" => "world", "temp" => "unknown"}, ~w(title temp)a)
    179 
    180     assert %Post{visits: 17, title: "world", temp: "unknown"} = TestRepo.update!(changeset)
    181     assert %Post{visits: 13, title: "world", temp: "temp"} = TestRepo.get!(Post, post.id)
    182   end
    183 
    184   test "insert and update with empty changeset" do
    185     # On insert we merge the fields and changes
    186     changeset = Ecto.Changeset.cast(%Permalink{}, %{}, ~w())
    187     assert %Permalink{} = permalink = TestRepo.insert!(changeset)
    188 
    189     # Assert we can update the same value twice,
    190     # without changes, without triggering stale errors.
    191     changeset = Ecto.Changeset.cast(permalink, %{}, ~w())
    192     assert TestRepo.update!(changeset) == permalink
    193     assert TestRepo.update!(changeset) == permalink
    194   end
    195 
    196   @tag :no_primary_key
    197   test "insert with no primary key" do
    198     assert %Barebone{num: nil} = TestRepo.insert!(%Barebone{})
    199     assert %Barebone{num: 13} = TestRepo.insert!(%Barebone{num: 13})
    200   end
    201 
    202   @tag :read_after_writes
    203   test "insert and update with changeset read after writes" do
    204     defmodule RAW do
    205       use Ecto.Schema
    206 
    207       schema "comments" do
    208         field :text, :string
    209         field :lock_version, :integer, read_after_writes: true
    210       end
    211     end
    212 
    213     changeset = Ecto.Changeset.cast(struct(RAW, %{}), %{}, ~w())
    214 
    215     # If the field is nil, we will not send it
    216     # and read the value back from the database.
    217     assert %{id: cid, lock_version: 1} = raw = TestRepo.insert!(changeset)
    218 
    219     # Set the counter to 11, so we can read it soon
    220     TestRepo.update_all from(u in RAW, where: u.id == ^cid), set: [lock_version: 11]
    221 
    222     # We will read back on update too
    223     changeset = Ecto.Changeset.cast(raw, %{"text" => "0"}, ~w(text)a)
    224     assert %{id: ^cid, lock_version: 11, text: "0"} = TestRepo.update!(changeset)
    225   end
    226 
    227   test "insert autogenerates for custom type" do
    228     post = TestRepo.insert!(%Post{uuid: nil})
    229     assert byte_size(post.uuid) == 36
    230     assert TestRepo.get_by(Post, uuid: post.uuid) == post
    231   end
    232 
    233   @tag :id_type
    234   test "insert autogenerates for custom id type" do
    235     defmodule ID do
    236       use Ecto.Schema
    237 
    238       @primary_key {:id, CustomPermalink, autogenerate: true}
    239       schema "posts" do
    240       end
    241     end
    242 
    243     id = TestRepo.insert!(struct(ID, id: nil))
    244     assert id.id
    245     assert TestRepo.get_by(ID, id: "#{id.id}-hello") == id
    246   end
    247 
    248   @tag :id_type
    249   @tag :assigns_id_type
    250   test "insert with user-assigned primary key" do
    251     assert %Post{id: 1} = TestRepo.insert!(%Post{id: 1})
    252   end
    253 
    254   @tag :id_type
    255   @tag :assigns_id_type
    256   test "insert and update with user-assigned primary key in changeset" do
    257     changeset = Ecto.Changeset.cast(%Post{id: 11}, %{"id" => "13"}, ~w(id)a)
    258     assert %Post{id: 13} = post = TestRepo.insert!(changeset)
    259 
    260     changeset = Ecto.Changeset.cast(post, %{"id" => "15"}, ~w(id)a)
    261     assert %Post{id: 15} = TestRepo.update!(changeset)
    262   end
    263 
    264   test "insert and fetch a schema with utc timestamps" do
    265     datetime = DateTime.from_unix!(System.os_time(:second), :second)
    266     TestRepo.insert!(%User{inserted_at: datetime})
    267     assert [%{inserted_at: ^datetime}] = TestRepo.all(User)
    268   end
    269 
    270   test "optimistic locking in update/delete operations" do
    271     import Ecto.Changeset, only: [cast: 3, optimistic_lock: 2]
    272     base_comment = TestRepo.insert!(%Comment{})
    273 
    274     changeset_ok =
    275       base_comment
    276       |> cast(%{"text" => "foo.bar"}, ~w(text)a)
    277       |> optimistic_lock(:lock_version)
    278     TestRepo.update!(changeset_ok)
    279 
    280     changeset_stale =
    281       base_comment
    282       |> cast(%{"text" => "foo.bat"}, ~w(text)a)
    283       |> optimistic_lock(:lock_version)
    284 
    285     assert_raise Ecto.StaleEntryError, fn -> TestRepo.update!(changeset_stale) end
    286     assert_raise Ecto.StaleEntryError, fn -> TestRepo.delete!(changeset_stale) end
    287   end
    288 
    289   test "optimistic locking in update operation with nil field" do
    290     import Ecto.Changeset, only: [cast: 3, optimistic_lock: 3]
    291 
    292     base_comment =
    293       %Comment{}
    294       |> cast(%{lock_version: nil}, [:lock_version])
    295       |> TestRepo.insert!()
    296 
    297     incrementer =
    298       fn
    299         nil -> 1
    300         old_value -> old_value + 1
    301       end
    302 
    303     changeset_ok =
    304       base_comment
    305       |> cast(%{"text" => "foo.bar"}, ~w(text)a)
    306       |> optimistic_lock(:lock_version, incrementer)
    307 
    308     updated = TestRepo.update!(changeset_ok)
    309     assert updated.text == "foo.bar"
    310     assert updated.lock_version == 1
    311   end
    312 
    313   test "optimistic locking in delete operation with nil field" do
    314     import Ecto.Changeset, only: [cast: 3, optimistic_lock: 3]
    315 
    316     base_comment =
    317       %Comment{}
    318       |> cast(%{lock_version: nil}, [:lock_version])
    319       |> TestRepo.insert!()
    320 
    321     incrementer =
    322       fn
    323         nil -> 1
    324         old_value -> old_value + 1
    325       end
    326 
    327     changeset_ok = optimistic_lock(base_comment, :lock_version, incrementer)
    328     TestRepo.delete!(changeset_ok)
    329 
    330     refute TestRepo.get(Comment, base_comment.id)
    331   end
    332 
    333   @tag :unique_constraint
    334   test "unique constraint" do
    335     changeset = Ecto.Changeset.change(%Post{}, uuid: Ecto.UUID.generate())
    336     {:ok, _}  = TestRepo.insert(changeset)
    337 
    338     exception =
    339       assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn ->
    340         changeset
    341         |> TestRepo.insert()
    342       end
    343 
    344     assert exception.message =~ "posts_uuid_index (unique_constraint)"
    345     assert exception.message =~ "The changeset has not defined any constraint."
    346     assert exception.message =~ "call `unique_constraint/3`"
    347 
    348     message = ~r/constraint error when attempting to insert struct/
    349     exception =
    350       assert_raise Ecto.ConstraintError, message, fn ->
    351         changeset
    352         |> Ecto.Changeset.unique_constraint(:uuid, name: :posts_email_changeset)
    353         |> TestRepo.insert()
    354       end
    355 
    356     assert exception.message =~ "posts_email_changeset (unique_constraint)"
    357 
    358     {:error, changeset} =
    359       changeset
    360       |> Ecto.Changeset.unique_constraint(:uuid)
    361       |> TestRepo.insert()
    362     assert changeset.errors == [uuid: {"has already been taken", [constraint: :unique, constraint_name: "posts_uuid_index"]}]
    363     assert changeset.data.__meta__.state == :built
    364   end
    365 
    366   @tag :unique_constraint
    367   test "unique constraint from association" do
    368     uuid = Ecto.UUID.generate()
    369     post = & %Post{} |> Ecto.Changeset.change(uuid: &1) |> Ecto.Changeset.unique_constraint(:uuid)
    370 
    371     {:error, changeset} =
    372       TestRepo.insert %User{
    373         comments: [%Comment{}],
    374         permalink: %Permalink{},
    375         posts: [post.(uuid), post.(uuid), post.(Ecto.UUID.generate())]
    376       }
    377 
    378     [_, p2, _] = changeset.changes.posts
    379     assert p2.errors == [uuid: {"has already been taken", [constraint: :unique, constraint_name: "posts_uuid_index"]}]
    380   end
    381 
    382   @tag :id_type
    383   @tag :unique_constraint
    384   test "unique constraint with binary_id" do
    385     changeset = Ecto.Changeset.change(%Custom{}, uuid: Ecto.UUID.generate())
    386     {:ok, _}  = TestRepo.insert(changeset)
    387 
    388     {:error, changeset} =
    389       changeset
    390       |> Ecto.Changeset.unique_constraint(:uuid)
    391       |> TestRepo.insert()
    392     assert changeset.errors == [uuid: {"has already been taken", [constraint: :unique, constraint_name: "customs_uuid_index"]}]
    393     assert changeset.data.__meta__.state == :built
    394   end
    395 
    396   test "unique pseudo-constraint violation error message with join table at the repository" do
    397     post =
    398       TestRepo.insert!(%Post{title: "some post"})
    399       |> TestRepo.preload(:unique_users)
    400 
    401     user =
    402       TestRepo.insert!(%User{name: "some user"})
    403 
    404     # Violate the unique composite index
    405     {:error, changeset} =
    406       post
    407       |> Ecto.Changeset.change
    408       |> Ecto.Changeset.put_assoc(:unique_users, [user, user])
    409       |> TestRepo.update
    410 
    411     errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
    412     assert errors == %{unique_users: [%{}, %{id: ["has already been taken"]}]}
    413     refute changeset.valid?
    414   end
    415 
    416   @tag :join
    417   @tag :unique_constraint
    418   test "unique constraint violation error message with join table in single changeset" do
    419     post =
    420       TestRepo.insert!(%Post{title: "some post"})
    421       |> TestRepo.preload(:constraint_users)
    422 
    423     user =
    424       TestRepo.insert!(%User{name: "some user"})
    425 
    426     # Violate the unique composite index
    427     {:error, changeset} =
    428       post
    429       |> Ecto.Changeset.change
    430       |> Ecto.Changeset.put_assoc(:constraint_users, [user, user])
    431       |> Ecto.Changeset.unique_constraint(:user,
    432           name: :posts_users_composite_pk_post_id_user_id_index,
    433           message: "has already been assigned")
    434       |> TestRepo.update
    435 
    436     errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
    437     assert errors == %{constraint_users: [%{}, %{user: ["has already been assigned"]}]}
    438 
    439     refute changeset.valid?
    440   end
    441 
    442   @tag :join
    443   @tag :unique_constraint
    444   test "unique constraint violation error message with join table and separate changesets" do
    445     post =
    446       TestRepo.insert!(%Post{title: "some post"})
    447       |> TestRepo.preload(:constraint_users)
    448 
    449     user = TestRepo.insert!(%User{name: "some user"})
    450 
    451     post
    452     |> Ecto.Changeset.change
    453     |> Ecto.Changeset.put_assoc(:constraint_users, [user])
    454     |> TestRepo.update
    455 
    456     # Violate the unique composite index
    457     {:error, changeset} =
    458       post
    459       |> Ecto.Changeset.change
    460       |> Ecto.Changeset.put_assoc(:constraint_users, [user])
    461       |> Ecto.Changeset.unique_constraint(:user,
    462           name: :posts_users_composite_pk_post_id_user_id_index,
    463           message: "has already been assigned")
    464       |> TestRepo.update
    465 
    466     errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
    467     assert errors == %{constraint_users: [%{user: ["has already been assigned"]}]}
    468 
    469     refute changeset.valid?
    470   end
    471 
    472   @tag :foreign_key_constraint
    473   test "foreign key constraint" do
    474     changeset = Ecto.Changeset.change(%Comment{post_id: 0})
    475 
    476     exception =
    477       assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn ->
    478         changeset
    479         |> TestRepo.insert()
    480       end
    481 
    482     assert exception.message =~ "comments_post_id_fkey (foreign_key_constraint)"
    483     assert exception.message =~ "The changeset has not defined any constraint."
    484     assert exception.message =~ "call `foreign_key_constraint/3`"
    485 
    486     message = ~r/constraint error when attempting to insert struct/
    487     exception =
    488       assert_raise Ecto.ConstraintError, message, fn ->
    489         changeset
    490         |> Ecto.Changeset.foreign_key_constraint(:post_id, name: :comments_post_id_other)
    491         |> TestRepo.insert()
    492       end
    493 
    494     assert exception.message =~ "comments_post_id_other (foreign_key_constraint)"
    495 
    496     {:error, changeset} =
    497       changeset
    498       |> Ecto.Changeset.foreign_key_constraint(:post_id)
    499       |> TestRepo.insert()
    500     assert changeset.errors == [post_id: {"does not exist", [constraint: :foreign, constraint_name: "comments_post_id_fkey"]}]
    501   end
    502 
    503   @tag :foreign_key_constraint
    504   test "assoc constraint" do
    505     changeset = Ecto.Changeset.change(%Comment{post_id: 0})
    506 
    507     exception =
    508       assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn ->
    509         changeset
    510         |> TestRepo.insert()
    511       end
    512 
    513     assert exception.message =~ "comments_post_id_fkey (foreign_key_constraint)"
    514     assert exception.message =~ "The changeset has not defined any constraint."
    515 
    516     message = ~r/constraint error when attempting to insert struct/
    517     exception =
    518       assert_raise Ecto.ConstraintError, message, fn ->
    519         changeset
    520         |> Ecto.Changeset.assoc_constraint(:post, name: :comments_post_id_other)
    521         |> TestRepo.insert()
    522       end
    523 
    524     assert exception.message =~ "comments_post_id_other (foreign_key_constraint)"
    525 
    526     {:error, changeset} =
    527       changeset
    528       |> Ecto.Changeset.assoc_constraint(:post)
    529       |> TestRepo.insert()
    530     assert changeset.errors == [post: {"does not exist", [constraint: :assoc, constraint_name: "comments_post_id_fkey"]}]
    531   end
    532 
    533   @tag :foreign_key_constraint
    534   test "no assoc constraint error" do
    535     user = TestRepo.insert!(%User{})
    536     TestRepo.insert!(%Permalink{user_id: user.id})
    537 
    538     exception =
    539       assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to delete struct/, fn ->
    540         TestRepo.delete!(user)
    541       end
    542 
    543     assert exception.message =~ "permalinks_user_id_fkey (foreign_key_constraint)"
    544     assert exception.message =~ "The changeset has not defined any constraint."
    545   end
    546 
    547   @tag :foreign_key_constraint
    548   test "no assoc constraint with changeset mismatch" do
    549     user = TestRepo.insert!(%User{})
    550     TestRepo.insert!(%Permalink{user_id: user.id})
    551 
    552     message = ~r/constraint error when attempting to delete struct/
    553     exception =
    554       assert_raise Ecto.ConstraintError, message, fn ->
    555         user
    556         |> Ecto.Changeset.change
    557         |> Ecto.Changeset.no_assoc_constraint(:permalink, name: :permalinks_user_id_pther)
    558         |> TestRepo.delete()
    559       end
    560 
    561     assert exception.message =~ "permalinks_user_id_pther (foreign_key_constraint)"
    562   end
    563 
    564   @tag :foreign_key_constraint
    565   test "no assoc constraint with changeset match" do
    566     user = TestRepo.insert!(%User{})
    567     TestRepo.insert!(%Permalink{user_id: user.id})
    568 
    569     {:error, changeset} =
    570       user
    571       |> Ecto.Changeset.change
    572       |> Ecto.Changeset.no_assoc_constraint(:permalink)
    573       |> TestRepo.delete()
    574     assert changeset.errors == [permalink: {"is still associated with this entry", [constraint: :no_assoc, constraint_name: "permalinks_user_id_fkey"]}]
    575   end
    576 
    577   @tag :foreign_key_constraint
    578   test "insert and update with embeds during failing child foreign key" do
    579     changeset =
    580       Order
    581       |> struct(%{})
    582       |> order_changeset(%{item: %{price: 10}, permalink: %{post_id: 0}})
    583 
    584     {:error, changeset} = TestRepo.insert(changeset)
    585     assert %Ecto.Changeset{} = changeset.changes.item
    586 
    587     order =
    588       Order
    589       |> struct(%{})
    590       |> order_changeset(%{})
    591       |> TestRepo.insert!()
    592       |> TestRepo.preload([:permalink])
    593 
    594     changeset = order_changeset(order, %{item: %{price: 10}, permalink: %{post_id: 0}})
    595     assert %Ecto.Changeset{} = changeset.changes.item
    596 
    597     {:error, changeset} = TestRepo.update(changeset)
    598     assert %Ecto.Changeset{} = changeset.changes.item
    599   end
    600 
    601   def order_changeset(order, params) do
    602     order
    603     |> Ecto.Changeset.cast(params, [:permalink_id])
    604     |> Ecto.Changeset.cast_embed(:item, with: &item_changeset/2)
    605     |> Ecto.Changeset.cast_assoc(:permalink, with: &permalink_changeset/2)
    606   end
    607 
    608   def item_changeset(item, params) do
    609     item
    610     |> Ecto.Changeset.cast(params, [:price])
    611   end
    612 
    613   def permalink_changeset(comment, params) do
    614     comment
    615     |> Ecto.Changeset.cast(params, [:post_id])
    616     |> Ecto.Changeset.assoc_constraint(:post)
    617   end
    618 
    619   test "unsafe_validate_unique/4" do
    620     {:ok, inserted_post} = TestRepo.insert(%Post{title: "Greetings", visits: 13})
    621     new_post_changeset = Post.changeset(%Post{}, %{title: "Greetings", visits: 17})
    622 
    623     changeset = Ecto.Changeset.unsafe_validate_unique(new_post_changeset, [:title], TestRepo)
    624     assert changeset.errors[:title] ==
    625            {"has already been taken", validation: :unsafe_unique, fields: [:title]}
    626 
    627     changeset = Ecto.Changeset.unsafe_validate_unique(new_post_changeset, [:title, :text], TestRepo)
    628     assert changeset.errors[:title] == nil
    629 
    630     update_changeset = Post.changeset(inserted_post, %{visits: 17})
    631     changeset = Ecto.Changeset.unsafe_validate_unique(update_changeset, [:title], TestRepo)
    632     assert changeset.errors[:title] == nil # cannot conflict with itself
    633   end
    634 
    635   test "unsafe_validate_unique/4 with composite keys" do
    636     {:ok, inserted_post} = TestRepo.insert(%CompositePk{a: 123, b: 456, name: "UniqueName"})
    637 
    638     different_pk = CompositePk.changeset(%CompositePk{}, %{name: "UniqueName", a: 789, b: 321})
    639     changeset = Ecto.Changeset.unsafe_validate_unique(different_pk, [:name], TestRepo)
    640     assert changeset.errors[:name] ==
    641       {"has already been taken", validation: :unsafe_unique, fields: [:name]}
    642 
    643     partial_pk = CompositePk.changeset(%CompositePk{}, %{name: "UniqueName", a: 789, b: 456})
    644     changeset = Ecto.Changeset.unsafe_validate_unique(partial_pk, [:name], TestRepo)
    645     assert changeset.errors[:name] ==
    646            {"has already been taken", validation: :unsafe_unique, fields: [:name]}
    647 
    648     update_changeset = CompositePk.changeset(inserted_post, %{name: "NewName"})
    649     changeset = Ecto.Changeset.unsafe_validate_unique(update_changeset, [:name], TestRepo)
    650     assert changeset.valid?
    651     assert changeset.errors[:name] == nil # cannot conflict with itself
    652   end
    653 
    654   test "get(!)" do
    655     post1 = TestRepo.insert!(%Post{title: "1"})
    656     post2 = TestRepo.insert!(%Post{title: "2"})
    657 
    658     assert post1 == TestRepo.get(Post, post1.id)
    659     assert post2 == TestRepo.get(Post, to_string post2.id) # With casting
    660 
    661     assert post1 == TestRepo.get!(Post, post1.id)
    662     assert post2 == TestRepo.get!(Post, to_string post2.id) # With casting
    663 
    664     TestRepo.delete!(post1)
    665 
    666     assert TestRepo.get(Post, post1.id) == nil
    667     assert_raise Ecto.NoResultsError, fn ->
    668       TestRepo.get!(Post, post1.id)
    669     end
    670   end
    671 
    672   test "get(!) with custom source" do
    673     custom = Ecto.put_meta(%Custom{}, source: "posts")
    674     custom = TestRepo.insert!(custom)
    675     bid    = custom.bid
    676     assert %Custom{bid: ^bid, __meta__: %{source: "posts"}} =
    677            TestRepo.get(from(c in {"posts", Custom}), bid)
    678   end
    679 
    680   test "get(!) with binary_id" do
    681     custom = TestRepo.insert!(%Custom{})
    682     bid = custom.bid
    683     assert %Custom{bid: ^bid} = TestRepo.get(Custom, bid)
    684   end
    685 
    686   test "get_by(!)" do
    687     post1 = TestRepo.insert!(%Post{title: "1", visits: 1})
    688     post2 = TestRepo.insert!(%Post{title: "2", visits: 2})
    689 
    690     assert post1 == TestRepo.get_by(Post, id: post1.id)
    691     assert post1 == TestRepo.get_by(Post, title: post1.title)
    692     assert post1 == TestRepo.get_by(Post, id: post1.id, title: post1.title)
    693     assert post2 == TestRepo.get_by(Post, id: to_string(post2.id)) # With casting
    694     assert nil   == TestRepo.get_by(Post, title: "hey")
    695     assert nil   == TestRepo.get_by(Post, id: post2.id, visits: 3)
    696 
    697     assert post1 == TestRepo.get_by!(Post, id: post1.id)
    698     assert post1 == TestRepo.get_by!(Post, title: post1.title)
    699     assert post1 == TestRepo.get_by!(Post, id: post1.id, visits: 1)
    700     assert post2 == TestRepo.get_by!(Post, id: to_string(post2.id)) # With casting
    701 
    702     assert post1 == TestRepo.get_by!(Post, %{id: post1.id})
    703 
    704     assert_raise Ecto.NoResultsError, fn ->
    705       TestRepo.get_by!(Post, id: post2.id, title: "hey")
    706     end
    707   end
    708 
    709   test "reload" do
    710     post1 = TestRepo.insert!(%Post{title: "1", visits: 1})
    711     post2 = TestRepo.insert!(%Post{title: "2", visits: 2})
    712 
    713     assert post1 == TestRepo.reload(post1)
    714     assert [post1, post2] == TestRepo.reload([post1, post2])
    715     assert [post1, post2, nil] == TestRepo.reload([post1, post2, %Post{id: 0}])
    716     assert nil == TestRepo.reload(%Post{id: 0})
    717 
    718     # keeps order as received in the params
    719     assert [post2, post1] == TestRepo.reload([post2, post1])
    720 
    721     TestRepo.update_all(Post, inc: [visits: 1])
    722 
    723     assert [%{visits: 2}, %{visits: 3}] = TestRepo.reload([post1, post2])
    724   end
    725 
    726   test "reload ignores preloads" do
    727     post = TestRepo.insert!(%Post{title: "1", visits: 1}) |> TestRepo.preload(:comments)
    728 
    729     assert %{comments: %Ecto.Association.NotLoaded{}} = TestRepo.reload(post)
    730   end
    731 
    732   test "reload!" do
    733     post1 = TestRepo.insert!(%Post{title: "1", visits: 1})
    734     post2 = TestRepo.insert!(%Post{title: "2", visits: 2})
    735 
    736     assert post1 == TestRepo.reload!(post1)
    737     assert [post1, post2] == TestRepo.reload!([post1, post2])
    738 
    739     assert_raise RuntimeError, ~r"could not reload", fn ->
    740       TestRepo.reload!([post1, post2, %Post{id: -1}])
    741     end
    742 
    743     assert_raise Ecto.NoResultsError, fn ->
    744       TestRepo.reload!(%Post{id: -1})
    745     end
    746 
    747     assert [post2, post1] == TestRepo.reload([post2, post1])
    748 
    749     TestRepo.update_all(Post, inc: [visits: 1])
    750 
    751     assert [%{visits: 2}, %{visits: 3}] = TestRepo.reload!([post1, post2])
    752   end
    753 
    754   test "first, last and one(!)" do
    755     post1 = TestRepo.insert!(%Post{title: "1"})
    756     post2 = TestRepo.insert!(%Post{title: "2"})
    757 
    758     assert post1 == Post |> first |> TestRepo.one
    759     assert post2 == Post |> last |> TestRepo.one
    760 
    761     query = from p in Post, order_by: p.title
    762     assert post1 == query |> first |> TestRepo.one
    763     assert post2 == query |> last |> TestRepo.one
    764 
    765     query = from p in Post, order_by: [desc: p.title], limit: 10
    766     assert post2 == query |> first |> TestRepo.one
    767     assert post1 == query |> last |> TestRepo.one
    768 
    769     query = from p in Post, where: is_nil(p.id)
    770     refute query |> first |> TestRepo.one
    771     refute query |> last |> TestRepo.one
    772     assert_raise Ecto.NoResultsError, fn -> query |> first |> TestRepo.one! end
    773     assert_raise Ecto.NoResultsError, fn -> query |> last |> TestRepo.one! end
    774   end
    775 
    776   test "exists?" do
    777     TestRepo.insert!(%Post{title: "1", visits: 2})
    778     TestRepo.insert!(%Post{title: "2", visits: 1})
    779 
    780     query = from p in Post, where: not is_nil(p.title), limit: 2
    781     assert query |> TestRepo.exists? == true
    782 
    783     query = from p in Post, where: p.title == "1", select: p.title
    784     assert query |> TestRepo.exists? == true
    785 
    786     query = from p in Post, where: is_nil(p.id)
    787     assert query |> TestRepo.exists? == false
    788 
    789     query = from p in Post, where: is_nil(p.id)
    790     assert query |> TestRepo.exists? == false
    791 
    792     query = from(p in Post, select: {p.visits, avg(p.visits)}, group_by: p.visits, having: avg(p.visits) > 1)
    793     assert query |> TestRepo.exists? == true
    794   end
    795 
    796   test "aggregate" do
    797     assert TestRepo.aggregate(Post, :max, :visits) == nil
    798 
    799     TestRepo.insert!(%Post{visits: 10})
    800     TestRepo.insert!(%Post{visits: 12})
    801     TestRepo.insert!(%Post{visits: 14})
    802     TestRepo.insert!(%Post{visits: 14})
    803 
    804     # Barebones
    805     assert TestRepo.aggregate(Post, :max, :visits) == 14
    806     assert TestRepo.aggregate(Post, :min, :visits) == 10
    807     assert TestRepo.aggregate(Post, :count, :visits) == 4
    808     assert "50" = to_string(TestRepo.aggregate(Post, :sum, :visits))
    809 
    810     # With order_by
    811     query = from Post, order_by: [asc: :visits]
    812     assert TestRepo.aggregate(query, :max, :visits) == 14
    813 
    814     # With order_by and limit
    815     query = from Post, order_by: [asc: :visits], limit: 2
    816     assert TestRepo.aggregate(query, :max, :visits) == 12
    817   end
    818 
    819   @tag :decimal_precision
    820   test "aggregate avg" do
    821     TestRepo.insert!(%Post{visits: 10})
    822     TestRepo.insert!(%Post{visits: 12})
    823     TestRepo.insert!(%Post{visits: 14})
    824     TestRepo.insert!(%Post{visits: 14})
    825 
    826     assert "12.5" <> _ = to_string(TestRepo.aggregate(Post, :avg, :visits))
    827   end
    828 
    829   @tag :inline_order_by
    830   test "aggregate with distinct" do
    831     TestRepo.insert!(%Post{visits: 10})
    832     TestRepo.insert!(%Post{visits: 12})
    833     TestRepo.insert!(%Post{visits: 14})
    834     TestRepo.insert!(%Post{visits: 14})
    835 
    836     query = from Post, order_by: [asc: :visits], distinct: true
    837     assert TestRepo.aggregate(query, :count, :visits) == 3
    838   end
    839 
    840   @tag :insert_cell_wise_defaults
    841   test "insert all" do
    842     assert {2, nil} = TestRepo.insert_all("comments", [[text: "1"], %{text: "2", lock_version: 2}])
    843     assert {2, nil} = TestRepo.insert_all({"comments", Comment}, [[text: "3"], %{text: "4", lock_version: 2}])
    844     assert [%Comment{text: "1", lock_version: 1},
    845             %Comment{text: "2", lock_version: 2},
    846             %Comment{text: "3", lock_version: 1},
    847             %Comment{text: "4", lock_version: 2}] = TestRepo.all(Comment)
    848 
    849     assert {2, nil} = TestRepo.insert_all(Post, [[], []])
    850     assert [%Post{}, %Post{}] = TestRepo.all(Post)
    851 
    852     assert {0, nil} = TestRepo.insert_all("posts", [])
    853     assert {0, nil} = TestRepo.insert_all({"posts", Post}, [])
    854   end
    855 
    856   @tag :insert_select
    857   test "insert all with query for single fields" do
    858     comment = TestRepo.insert!(%Comment{text: "1", lock_version: 1})
    859 
    860     text_query = from(c in Comment, select: c.text, where: [id: ^comment.id, lock_version: 1])
    861 
    862     lock_version_query = from(c in Comment, select: c.lock_version, where: [id: ^comment.id])
    863 
    864     rows = [
    865       [text: "2", lock_version: lock_version_query],
    866       [lock_version: lock_version_query, text: "3"],
    867       [text: text_query],
    868       [text: text_query, lock_version: lock_version_query],
    869       [lock_version: 6, text: "6"]
    870     ]
    871     assert {5, nil} = TestRepo.insert_all(Comment, rows, [])
    872 
    873     inserted_rows = Comment
    874                     |> where([c], c.id != ^comment.id)
    875                     |> TestRepo.all()
    876 
    877     assert [%Comment{text: "2", lock_version: 1},
    878             %Comment{text: "3", lock_version: 1},
    879             %Comment{text: "1"},
    880             %Comment{text: "1", lock_version: 1},
    881             %Comment{text: "6", lock_version: 6}] = inserted_rows
    882   end
    883 
    884   describe "insert_all with source query" do
    885     @tag :upsert_all
    886     @tag :with_conflict_target
    887     @tag :concat
    888     test "insert_all with query and conflict target" do
    889       {:ok, %Post{id: id}} = TestRepo.insert(%Post{
    890         title: "A generic title"
    891       })
    892 
    893       source = from p in Post,
    894         select: %{
    895           title: fragment("concat(?, ?, ?)", p.title, type(^" suffix ", :string), p.id)
    896         }
    897 
    898       assert {1, _} = TestRepo.insert_all(Post, source, conflict_target: [:id], on_conflict: :replace_all)
    899 
    900       expected_id = id + 1
    901       expected_title = "A generic title suffix #{id}"
    902 
    903       assert %Post{title: ^expected_title} = TestRepo.get(Post, expected_id)
    904     end
    905 
    906     @tag :returning
    907     @tag :concat
    908     test "insert_all with query and returning" do
    909       {:ok, %Post{id: id}} = TestRepo.insert(%Post{
    910         title: "A generic title"
    911       })
    912 
    913       source = from p in Post,
    914         select: %{
    915           title: fragment("concat(?, ?, ?)", p.title, type(^" suffix ", :string), p.id)
    916         }
    917 
    918       assert {1, returns} = TestRepo.insert_all(Post, source, returning: [:id, :title])
    919 
    920       expected_id = id + 1
    921       expected_title = "A generic title suffix #{id}"
    922       assert [%Post{id: ^expected_id, title: ^expected_title}] = returns
    923     end
    924 
    925     @tag :upsert_all
    926     @tag :without_conflict_target
    927     @tag :concat
    928     test "insert_all with query and on_conflict" do
    929       {:ok, %Post{id: id}} = TestRepo.insert(%Post{
    930         title: "A generic title"
    931       })
    932 
    933       source = from p in Post,
    934         select: %{
    935           title: fragment("concat(?, ?, ?)", p.title, type(^" suffix ", :string), p.id)
    936         }
    937 
    938       assert {1, _} = TestRepo.insert_all(Post, source, on_conflict: :replace_all)
    939 
    940       expected_id = id + 1
    941       expected_title = "A generic title suffix #{id}"
    942 
    943       assert %Post{title: ^expected_title} = TestRepo.get(Post, expected_id)
    944     end
    945 
    946     @tag :concat
    947     test "insert_all with query" do
    948       {:ok, %Post{id: id}} = TestRepo.insert(%Post{
    949         title: "A generic title"
    950       })
    951 
    952       source = from p in Post,
    953         select: %{
    954           title: fragment("concat(?, ?, ?)", p.title, type(^" suffix ", :string), p.id)
    955         }
    956 
    957       assert {1, _} = TestRepo.insert_all(Post, source)
    958 
    959       expected_id = id + 1
    960       expected_title = "A generic title suffix #{id}"
    961 
    962       assert %Post{title: ^expected_title} = TestRepo.get(Post, expected_id)
    963     end
    964   end
    965 
    966   @tag :invalid_prefix
    967   @tag :insert_cell_wise_defaults
    968   test "insert all with invalid prefix" do
    969     assert catch_error(TestRepo.insert_all(Post, [[], []], prefix: "oops"))
    970   end
    971 
    972   @tag :returning
    973   @tag :insert_cell_wise_defaults
    974   test "insert all with returning with schema" do
    975     assert {0, []} = TestRepo.insert_all(Comment, [], returning: true)
    976     assert {0, nil} = TestRepo.insert_all(Comment, [], returning: false)
    977 
    978     {2, [c1, c2]} = TestRepo.insert_all(Comment, [[text: "1"], [text: "2"]], returning: [:id, :text])
    979     assert %Comment{text: "1", __meta__: %{state: :loaded}} = c1
    980     assert %Comment{text: "2", __meta__: %{state: :loaded}} = c2
    981 
    982     {2, [c1, c2]} = TestRepo.insert_all(Comment, [[text: "3"], [text: "4"]], returning: true)
    983     assert %Comment{text: "3", __meta__: %{state: :loaded}} = c1
    984     assert %Comment{text: "4", __meta__: %{state: :loaded}} = c2
    985   end
    986 
    987   @tag :returning
    988   @tag :insert_cell_wise_defaults
    989   test "insert all with returning with schema with field source" do
    990     assert {0, []} = TestRepo.insert_all(Permalink, [], returning: true)
    991     assert {0, nil} = TestRepo.insert_all(Permalink, [], returning: false)
    992 
    993     {2, [c1, c2]} = TestRepo.insert_all(Permalink, [[url: "1"], [url: "2"]], returning: [:id, :url])
    994     assert %Permalink{url: "1", __meta__: %{state: :loaded}} = c1
    995     assert %Permalink{url: "2", __meta__: %{state: :loaded}} = c2
    996 
    997     {2, [c1, c2]} = TestRepo.insert_all(Permalink, [[url: "3"], [url: "4"]], returning: true)
    998     assert %Permalink{url: "3", __meta__: %{state: :loaded}} = c1
    999     assert %Permalink{url: "4", __meta__: %{state: :loaded}} = c2
   1000   end
   1001 
   1002   @tag :returning
   1003   @tag :insert_cell_wise_defaults
   1004   test "insert all with returning without schema" do
   1005     {2, [c1, c2]} = TestRepo.insert_all("comments", [[text: "1"], [text: "2"]], returning: [:id, :text])
   1006     assert %{id: _, text: "1"} = c1
   1007     assert %{id: _, text: "2"} = c2
   1008 
   1009     assert_raise ArgumentError, fn ->
   1010       TestRepo.insert_all("comments", [[text: "1"], [text: "2"]], returning: true)
   1011     end
   1012   end
   1013 
   1014   @tag :insert_cell_wise_defaults
   1015   test "insert all with dumping" do
   1016     uuid = Ecto.UUID.generate()
   1017     assert {1, nil} = TestRepo.insert_all(Post, [%{uuid: uuid}])
   1018     assert [%Post{uuid: ^uuid, title: nil}] = TestRepo.all(Post)
   1019   end
   1020 
   1021   @tag :insert_cell_wise_defaults
   1022   test "insert all autogenerates for binary_id type" do
   1023     custom = TestRepo.insert!(%Custom{bid: nil})
   1024     assert custom.bid
   1025     assert TestRepo.get(Custom, custom.bid)
   1026     assert TestRepo.delete!(custom)
   1027     refute TestRepo.get(Custom, custom.bid)
   1028 
   1029     uuid = Ecto.UUID.generate()
   1030     assert {2, nil} = TestRepo.insert_all(Custom, [%{uuid: uuid}, %{bid: custom.bid}])
   1031     assert [%Custom{bid: bid2, uuid: nil},
   1032             %Custom{bid: bid1, uuid: ^uuid}] = Enum.sort_by(TestRepo.all(Custom), & &1.uuid)
   1033     assert bid1 && bid2
   1034     assert custom.bid != bid1
   1035     assert custom.bid == bid2
   1036   end
   1037 
   1038   describe "placeholders" do
   1039     @describetag :placeholders
   1040 
   1041     test "Repo.insert_all fills in placeholders" do
   1042       placeholders = %{foo: 100, bar: "test"}
   1043       bar_ph = {:placeholder, :bar}
   1044       foo_ph = {:placeholder, :foo}
   1045 
   1046       entries = [
   1047         %{intensity: 1.0, title: bar_ph, posted: ~D[2020-12-21], visits: foo_ph},
   1048         %{intensity: 2.0, title: bar_ph, posted: ~D[2000-12-21], visits: foo_ph}
   1049       ] |> Enum.map(&Map.put(&1, :uuid, Ecto.UUID.generate))
   1050 
   1051       TestRepo.insert_all(Post, entries, placeholders: placeholders)
   1052 
   1053       query = from(p in Post, select: {p.intensity, p.title, p.visits})
   1054       assert [{1.0, "test", 100}, {2.0, "test", 100}] == TestRepo.all(query)
   1055     end
   1056 
   1057     test "Repo.insert_all accepts non-atom placeholder keys" do
   1058       placeholders = %{10 => "integer key", {:foo, :bar} => "tuple key"}
   1059       entries = [%{text: {:placeholder, 10}}, %{text: {:placeholder, {:foo, :bar}}}]
   1060       TestRepo.insert_all(Comment, entries, placeholders: placeholders)
   1061 
   1062       query = from(c in Comment, select: c.text)
   1063       assert ["integer key", "tuple key"] == TestRepo.all(query)
   1064     end
   1065 
   1066     test "Repo.insert_all fills in placeholders with keyword list entries" do
   1067       TestRepo.insert_all(Barebone, [[num: {:placeholder, :foo}]], placeholders: %{foo: 100})
   1068 
   1069       query = from(b in Barebone, select: b.num)
   1070       assert [100] == TestRepo.all(query)
   1071     end
   1072 
   1073     @tag :upsert_all
   1074     @tag :with_conflict_target
   1075     test "Repo.insert_all upserts and fills in placeholders with conditioned on_conflict query" do
   1076       do_not_update_title = "don't touch me"
   1077 
   1078       posted_value =
   1079         from p in Post, where: p.public == ^true and p.id > ^0, select: p.posted, limit: 1
   1080 
   1081       on_conflict =
   1082         from p in Post, update: [set: [title: "updated"]], where: p.title != ^do_not_update_title
   1083 
   1084       placeholders = %{visits: 1, title: "title"}
   1085 
   1086       post1 = [
   1087         visits: {:placeholder, :visits},
   1088         title: {:placeholder, :title},
   1089         uuid: Ecto.UUID.generate(),
   1090         posted: posted_value
   1091       ]
   1092 
   1093       post2 = [
   1094         title: do_not_update_title,
   1095         uuid: Ecto.UUID.generate(),
   1096         posted: posted_value
   1097       ]
   1098 
   1099       assert TestRepo.insert_all(Post, [post1, post2],
   1100                placeholders: placeholders,
   1101                on_conflict: on_conflict,
   1102                conflict_target: [:uuid]
   1103              ) ==
   1104                {2, nil}
   1105 
   1106       # only update first post
   1107       assert TestRepo.insert_all(Post, [post1, post2],
   1108                placeholders: placeholders,
   1109                on_conflict: on_conflict,
   1110                conflict_target: [:uuid]
   1111              ) ==
   1112                {1, nil}
   1113 
   1114       assert TestRepo.aggregate(where(Post, title: "updated"), :count) == 1
   1115     end
   1116   end
   1117 
   1118   test "update all" do
   1119     assert post1 = TestRepo.insert!(%Post{title: "1"})
   1120     assert post2 = TestRepo.insert!(%Post{title: "2"})
   1121     assert post3 = TestRepo.insert!(%Post{title: "3"})
   1122 
   1123     assert {3, nil} = TestRepo.update_all(Post, set: [title: "x"])
   1124 
   1125     assert %Post{title: "x"} = TestRepo.reload(post1)
   1126     assert %Post{title: "x"} = TestRepo.reload(post2)
   1127     assert %Post{title: "x"} = TestRepo.reload(post3)
   1128 
   1129     assert {3, nil} = TestRepo.update_all("posts", [set: [title: nil]])
   1130 
   1131     assert %Post{title: nil} = TestRepo.reload(post1)
   1132     assert %Post{title: nil} = TestRepo.reload(post2)
   1133     assert %Post{title: nil} = TestRepo.reload(post3)
   1134   end
   1135 
   1136   @tag :invalid_prefix
   1137   test "update all with invalid prefix" do
   1138     assert catch_error(TestRepo.update_all(Post, [set: [title: "x"]], prefix: "oops"))
   1139   end
   1140 
   1141   @tag :returning
   1142   test "update all with returning with schema" do
   1143     assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
   1144     assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
   1145     assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})
   1146 
   1147     assert {3, posts} = TestRepo.update_all(select(Post, [p], p), [set: [title: "x"]])
   1148 
   1149     [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
   1150     assert %Post{id: ^id1, title: "x"} = p1
   1151     assert %Post{id: ^id2, title: "x"} = p2
   1152     assert %Post{id: ^id3, title: "x"} = p3
   1153 
   1154     assert {3, posts} = TestRepo.update_all(select(Post, [:id, :visits]), [set: [visits: 11]])
   1155 
   1156     [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
   1157     assert %Post{id: ^id1, title: nil, visits: 11} = p1
   1158     assert %Post{id: ^id2, title: nil, visits: 11} = p2
   1159     assert %Post{id: ^id3, title: nil, visits: 11} = p3
   1160   end
   1161 
   1162   @tag :returning
   1163   test "update all with returning without schema" do
   1164     assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
   1165     assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
   1166     assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})
   1167 
   1168     assert {3, posts} = TestRepo.update_all(select("posts", [:id, :title]), [set: [title: "x"]])
   1169 
   1170     [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
   1171     assert p1 == %{id: id1, title: "x"}
   1172     assert p2 == %{id: id2, title: "x"}
   1173     assert p3 == %{id: id3, title: "x"}
   1174   end
   1175 
   1176   test "update all with filter" do
   1177     assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
   1178     assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
   1179     assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})
   1180 
   1181     query = from(p in Post, where: p.title == "1" or p.title == "2",
   1182                             update: [set: [visits: ^17]])
   1183     assert {2, nil} = TestRepo.update_all(query, set: [title: "x"])
   1184 
   1185     assert %Post{title: "x", visits: 17} = TestRepo.get(Post, id1)
   1186     assert %Post{title: "x", visits: 17} = TestRepo.get(Post, id2)
   1187     assert %Post{title: "3", visits: nil} = TestRepo.get(Post, id3)
   1188   end
   1189 
   1190   test "update all no entries" do
   1191     assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
   1192     assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
   1193     assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})
   1194 
   1195     query = from(p in Post, where: p.title == "4")
   1196     assert {0, nil} = TestRepo.update_all(query, set: [title: "x"])
   1197 
   1198     assert %Post{title: "1"} = TestRepo.get(Post, id1)
   1199     assert %Post{title: "2"} = TestRepo.get(Post, id2)
   1200     assert %Post{title: "3"} = TestRepo.get(Post, id3)
   1201   end
   1202 
   1203   test "update all increment syntax" do
   1204     assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1", visits: 0})
   1205     assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2", visits: 1})
   1206 
   1207     # Positive
   1208     query = from p in Post, where: not is_nil(p.id), update: [inc: [visits: 2]]
   1209     assert {2, nil} = TestRepo.update_all(query, [])
   1210 
   1211     assert %Post{visits: 2} = TestRepo.get(Post, id1)
   1212     assert %Post{visits: 3} = TestRepo.get(Post, id2)
   1213 
   1214     # Negative
   1215     query = from p in Post, where: not is_nil(p.id), update: [inc: [visits: -1]]
   1216     assert {2, nil} = TestRepo.update_all(query, [])
   1217 
   1218     assert %Post{visits: 1} = TestRepo.get(Post, id1)
   1219     assert %Post{visits: 2} = TestRepo.get(Post, id2)
   1220   end
   1221 
   1222   @tag :id_type
   1223   test "update all with casting and dumping on id type field" do
   1224     assert %Post{id: id1} = TestRepo.insert!(%Post{})
   1225     assert {1, nil} = TestRepo.update_all(Post, set: [counter: to_string(id1)])
   1226     assert %Post{counter: ^id1} = TestRepo.get(Post, id1)
   1227   end
   1228 
   1229   test "update all with casting and dumping" do
   1230     visits = 13
   1231     datetime = ~N[2014-01-16 20:26:51]
   1232     assert %Post{id: id} = TestRepo.insert!(%Post{})
   1233 
   1234     assert {1, nil} = TestRepo.update_all(Post, set: [visits: visits, inserted_at: datetime])
   1235     assert %Post{visits: 13, inserted_at: ^datetime} = TestRepo.get(Post, id)
   1236   end
   1237 
   1238   test "delete all" do
   1239     assert %Post{} = TestRepo.insert!(%Post{title: "1"})
   1240     assert %Post{} = TestRepo.insert!(%Post{title: "2"})
   1241     assert %Post{} = TestRepo.insert!(%Post{title: "3"})
   1242 
   1243     assert {3, nil} = TestRepo.delete_all(Post)
   1244     assert [] = TestRepo.all(Post)
   1245   end
   1246 
   1247   @tag :invalid_prefix
   1248   test "delete all with invalid prefix" do
   1249     assert catch_error(TestRepo.delete_all(Post, prefix: "oops"))
   1250   end
   1251 
   1252   @tag :returning
   1253   test "delete all with returning with schema" do
   1254     assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
   1255     assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
   1256     assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})
   1257 
   1258     assert {3, posts} = TestRepo.delete_all(select(Post, [p], p))
   1259 
   1260     [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
   1261     assert %Post{id: ^id1, title: "1"} = p1
   1262     assert %Post{id: ^id2, title: "2"} = p2
   1263     assert %Post{id: ^id3, title: "3"} = p3
   1264   end
   1265 
   1266   @tag :returning
   1267   test "delete all with returning without schema" do
   1268     assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
   1269     assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
   1270     assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})
   1271 
   1272     assert {3, posts} = TestRepo.delete_all(select("posts", [:id, :title]))
   1273 
   1274     [p1, p2, p3] = Enum.sort_by(posts, & &1.id)
   1275     assert p1 == %{id: id1, title: "1"}
   1276     assert p2 == %{id: id2, title: "2"}
   1277     assert p3 == %{id: id3, title: "3"}
   1278   end
   1279 
   1280   test "delete all with filter" do
   1281     assert %Post{} = TestRepo.insert!(%Post{title: "1"})
   1282     assert %Post{} = TestRepo.insert!(%Post{title: "2"})
   1283     assert %Post{} = TestRepo.insert!(%Post{title: "3"})
   1284 
   1285     query = from(p in Post, where: p.title == "1" or p.title == "2")
   1286     assert {2, nil} = TestRepo.delete_all(query)
   1287     assert [%Post{}] = TestRepo.all(Post)
   1288   end
   1289 
   1290   test "delete all no entries" do
   1291     assert %Post{id: id1} = TestRepo.insert!(%Post{title: "1"})
   1292     assert %Post{id: id2} = TestRepo.insert!(%Post{title: "2"})
   1293     assert %Post{id: id3} = TestRepo.insert!(%Post{title: "3"})
   1294 
   1295     query = from(p in Post, where: p.title == "4")
   1296     assert {0, nil} = TestRepo.delete_all(query)
   1297     assert %Post{title: "1"} = TestRepo.get(Post, id1)
   1298     assert %Post{title: "2"} = TestRepo.get(Post, id2)
   1299     assert %Post{title: "3"} = TestRepo.get(Post, id3)
   1300   end
   1301 
   1302   test "virtual field" do
   1303     assert %Post{id: id} = TestRepo.insert!(%Post{title: "1"})
   1304     assert TestRepo.get(Post, id).temp == "temp"
   1305   end
   1306 
   1307   ## Query syntax
   1308 
   1309   defmodule Foo do
   1310     defstruct [:title]
   1311   end
   1312 
   1313   describe "query select" do
   1314     test "expressions" do
   1315       %Post{} = TestRepo.insert!(%Post{title: "1", visits: 13})
   1316 
   1317       assert [{"1", 13}] ==
   1318              TestRepo.all(from p in Post, select: {p.title, p.visits})
   1319 
   1320       assert [["1", 13]] ==
   1321              TestRepo.all(from p in Post, select: [p.title, p.visits])
   1322 
   1323       assert [%{:title => "1", 3 => 13, "visits" => 13}] ==
   1324              TestRepo.all(from p in Post, select: %{
   1325                :title => p.title,
   1326                "visits" => p.visits,
   1327                3 => p.visits
   1328              })
   1329 
   1330       assert [%{:title => "1", "1" => 13, "visits" => 13}] ==
   1331              TestRepo.all(from p in Post, select: %{
   1332                :title  => p.title,
   1333                p.title => p.visits,
   1334                "visits"  => p.visits
   1335              })
   1336 
   1337       assert [%Foo{title: "1"}] ==
   1338              TestRepo.all(from p in Post, select: %Foo{title: p.title})
   1339     end
   1340 
   1341     test "map update" do
   1342       %Post{} = TestRepo.insert!(%Post{title: "1", visits: 13})
   1343 
   1344       assert [%Post{:title => "new title", visits: 13}] =
   1345              TestRepo.all(from p in Post, select: %{p | title: "new title"})
   1346 
   1347       assert [%Post{title: "new title", visits: 13}] =
   1348         TestRepo.all(from p in Post, select: %Post{p | title: "new title"})
   1349 
   1350       assert_raise KeyError, fn ->
   1351         TestRepo.all(from p in Post, select: %{p | unknown: "new title"})
   1352       end
   1353 
   1354       assert_raise BadMapError, fn ->
   1355         TestRepo.all(from p in Post, select: %{p.title | title: "new title"})
   1356       end
   1357 
   1358       assert_raise BadStructError, fn ->
   1359         TestRepo.all(from p in Post, select: %Foo{p | title: p.title})
   1360       end
   1361     end
   1362 
   1363     test "take with structs" do
   1364       %{id: pid1} = TestRepo.insert!(%Post{title: "1"})
   1365       %{id: pid2} = TestRepo.insert!(%Post{title: "2"})
   1366       %{id: pid3} = TestRepo.insert!(%Post{title: "3"})
   1367 
   1368       [p1, p2, p3] = Post |> select([p], struct(p, [:title])) |> order_by([:title]) |> TestRepo.all
   1369       refute p1.id
   1370       assert p1.title == "1"
   1371       assert match?(%Post{}, p1)
   1372       refute p2.id
   1373       assert p2.title == "2"
   1374       assert match?(%Post{}, p2)
   1375       refute p3.id
   1376       assert p3.title == "3"
   1377       assert match?(%Post{}, p3)
   1378 
   1379       [p1, p2, p3] = Post |> select([:id]) |> order_by([:id]) |> TestRepo.all
   1380       assert %Post{id: ^pid1} = p1
   1381       assert %Post{id: ^pid2} = p2
   1382       assert %Post{id: ^pid3} = p3
   1383     end
   1384 
   1385     test "take with maps" do
   1386       %{id: pid1} = TestRepo.insert!(%Post{title: "1"})
   1387       %{id: pid2} = TestRepo.insert!(%Post{title: "2"})
   1388       %{id: pid3} = TestRepo.insert!(%Post{title: "3"})
   1389 
   1390       [p1, p2, p3] = "posts" |> select([p], map(p, [:title])) |> order_by([:title]) |> TestRepo.all
   1391       assert p1 == %{title: "1"}
   1392       assert p2 == %{title: "2"}
   1393       assert p3 == %{title: "3"}
   1394 
   1395       [p1, p2, p3] = "posts" |> select([:id]) |> order_by([:id]) |> TestRepo.all
   1396       assert p1 == %{id: pid1}
   1397       assert p2 == %{id: pid2}
   1398       assert p3 == %{id: pid3}
   1399     end
   1400 
   1401     test "take with preload assocs" do
   1402       %{id: pid} = TestRepo.insert!(%Post{title: "post"})
   1403       TestRepo.insert!(%Comment{post_id: pid, text: "comment"})
   1404       fields = [:id, :title, comments: [:text, :post_id]]
   1405 
   1406       [p] = Post |> preload(:comments) |> select([p], ^fields) |> TestRepo.all
   1407       assert %Post{title: "post"} = p
   1408       assert [%Comment{text: "comment"}] = p.comments
   1409 
   1410       [p] = Post |> preload(:comments) |> select([p], struct(p, ^fields)) |> TestRepo.all
   1411       assert %Post{title: "post"} = p
   1412       assert [%Comment{text: "comment"}] = p.comments
   1413 
   1414       [p] = Post |> preload(:comments) |> select([p], map(p, ^fields)) |> TestRepo.all
   1415       assert p == %{id: pid, title: "post", comments: [%{text: "comment", post_id: pid}]}
   1416     end
   1417 
   1418     test "take with nil preload assoc" do
   1419       %{id: cid} = TestRepo.insert!(%Comment{text: "comment"})
   1420       fields = [:id, :text, post: [:title]]
   1421 
   1422       [c] = Comment |> preload(:post) |> select([c], ^fields) |> TestRepo.all
   1423       assert %Comment{id: ^cid, text: "comment", post: nil} = c
   1424 
   1425       [c] = Comment |> preload(:post) |> select([c], struct(c, ^fields)) |> TestRepo.all
   1426       assert %Comment{id: ^cid, text: "comment", post: nil} = c
   1427 
   1428       [c] = Comment |> preload(:post) |> select([c], map(c, ^fields)) |> TestRepo.all
   1429       assert c == %{id: cid, text: "comment", post: nil}
   1430     end
   1431 
   1432     test "take with join assocs" do
   1433       %{id: pid} = TestRepo.insert!(%Post{title: "post"})
   1434       %{id: cid} = TestRepo.insert!(%Comment{post_id: pid, text: "comment"})
   1435       fields = [:id, :title, comments: [:text, :post_id, :id]]
   1436       query = from p in Post, where: p.id == ^pid, join: c in assoc(p, :comments), preload: [comments: c]
   1437 
   1438       p = TestRepo.one(from q in query, select: ^fields)
   1439       assert %Post{title: "post"} = p
   1440       assert [%Comment{text: "comment"}] = p.comments
   1441 
   1442       p = TestRepo.one(from q in query, select: struct(q, ^fields))
   1443       assert %Post{title: "post"} = p
   1444       assert [%Comment{text: "comment"}] = p.comments
   1445 
   1446       p = TestRepo.one(from q in query, select: map(q, ^fields))
   1447       assert p == %{id: pid, title: "post", comments: [%{text: "comment", post_id: pid, id: cid}]}
   1448     end
   1449 
   1450     test "take with single nil column" do
   1451       %Post{} = TestRepo.insert!(%Post{title: "1", counter: nil})
   1452       assert %{counter: nil} =
   1453              TestRepo.one(from p in Post, where: p.title == "1", select: [:counter])
   1454     end
   1455 
   1456     test "take with join assocs and single nil column" do
   1457       %{id: post_id} = TestRepo.insert!(%Post{title: "1"}, counter: nil)
   1458       TestRepo.insert!(%Comment{post_id: post_id, text: "comment"})
   1459       assert %{counter: nil} ==
   1460               TestRepo.one(from p in Post, join: c in assoc(p, :comments), where: p.title == "1", select:  map(p, [:counter]))
   1461     end
   1462 
   1463     test "field source" do
   1464       TestRepo.insert!(%Permalink{url: "url"})
   1465       assert ["url"] = Permalink |> select([p], p.url) |> TestRepo.all()
   1466       assert [1] = Permalink |> select([p], count(p.url)) |> TestRepo.all()
   1467     end
   1468 
   1469     test "merge" do
   1470       date = Date.utc_today()
   1471       %Post{id: post_id} = TestRepo.insert!(%Post{title: "1", counter: nil, posted: date, public: false})
   1472 
   1473       # Merge on source
   1474       assert [%Post{title: "2"}] =
   1475              Post |> select([p], merge(p, %{title: "2"})) |> TestRepo.all()
   1476       assert [%Post{title: "2"}] =
   1477              Post |> select([p], p) |> select_merge([p], %{title: "2"}) |> TestRepo.all()
   1478 
   1479       # Merge on struct
   1480       assert [%Post{title: "2"}] =
   1481              Post |> select([p], merge(%Post{title: p.title}, %{title: "2"})) |> TestRepo.all()
   1482       assert [%Post{title: "2"}] =
   1483              Post |> select([p], %Post{title: p.title}) |> select_merge([p], %{title: "2"}) |> TestRepo.all()
   1484 
   1485       # Merge on map
   1486       assert [%{title: "2"}] =
   1487              Post |> select([p], merge(%{title: p.title}, %{title: "2"})) |> TestRepo.all()
   1488       assert [%{title: "2"}] =
   1489              Post |> select([p], %{title: p.title}) |> select_merge([p], %{title: "2"}) |> TestRepo.all()
   1490 
   1491       # Merge on outer join with map
   1492       %Permalink{} = TestRepo.insert!(%Permalink{post_id: post_id, url: "Q", title: "Z"})
   1493 
   1494       # left join record is present
   1495       assert [%{url: "Q", title: "1", posted: _date}] =
   1496                Permalink
   1497                |> join(:left, [l], p in Post, on: l.post_id == p.id)
   1498                |> select([l, p], merge(l, map(p, ^~w(title posted)a)))
   1499                |> TestRepo.all()
   1500 
   1501       assert [%{url: "Q", title: "1", posted: _date}] =
   1502                Permalink
   1503                |> join(:left, [l], p in Post, on: l.post_id == p.id)
   1504                |> select_merge([_l, p], map(p, ^~w(title posted)a))
   1505                |> TestRepo.all()
   1506 
   1507       # left join record is not present
   1508       assert [%{url: "Q", title: "Z", posted: nil}] =
   1509                Permalink
   1510                |> join(:left, [l], p in Post, on: l.post_id == p.id and p.public == true)
   1511                |> select([l, p], merge(l, map(p, ^~w(title posted)a)))
   1512                |> TestRepo.all()
   1513 
   1514       assert [%{url: "Q", title: "Z", posted: nil}] =
   1515                Permalink
   1516                |> join(:left, [l], p in Post, on: l.post_id == p.id and p.public == true)
   1517                |> select_merge([_l, p], map(p, ^~w(title posted)a))
   1518                |> TestRepo.all()
   1519     end
   1520 
   1521     test "merge with update on self" do
   1522       %Post{} = TestRepo.insert!(%Post{title: "1", counter: 1})
   1523 
   1524       assert [%Post{title: "1", counter: 2}] =
   1525         Post |> select([p], merge(p, %{p | counter: 2})) |> TestRepo.all()
   1526       assert [%Post{title: "1", counter: 2}] =
   1527         Post |> select([p], p) |> select_merge([p], %{p | counter: 2}) |> TestRepo.all()
   1528     end
   1529 
   1530     test "merge within subquery" do
   1531       %Post{} = TestRepo.insert!(%Post{title: "1", counter: 1})
   1532 
   1533       subquery =
   1534         Post
   1535         |> select_merge([p], %{p | counter: 2})
   1536         |> subquery()
   1537 
   1538       assert [%Post{title: "1", counter: 2}] = TestRepo.all(subquery)
   1539     end
   1540 
   1541     @tag :selected_as_with_group_by
   1542     test "selected_as/2 with group_by" do
   1543       TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 3})
   1544       TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 2})
   1545       TestRepo.insert!(%Post{posted: ~D[2020-12-20], visits: nil})
   1546 
   1547       query =
   1548         from p in Post,
   1549           select: %{
   1550             posted: selected_as(p.posted, :date),
   1551             min_visits: p.visits |> coalesce(0) |> min()
   1552           },
   1553           group_by: selected_as(:date),
   1554           order_by: p.posted
   1555 
   1556       assert [%{posted: ~D[2020-12-20], min_visits: 0}, %{posted: ~D[2020-12-21], min_visits: 2}] =
   1557                TestRepo.all(query)
   1558     end
   1559 
   1560     @tag :selected_as_with_order_by
   1561     test "selected_as/2 with order_by" do
   1562       TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 3})
   1563       TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 2})
   1564       TestRepo.insert!(%Post{posted: ~D[2020-12-20], visits: nil})
   1565 
   1566       base_query =
   1567         from p in Post,
   1568           select: %{
   1569             posted: p.posted,
   1570             min_visits: p.visits |> coalesce(0) |> min() |> selected_as(:min_visits)
   1571           },
   1572           group_by: p.posted
   1573 
   1574       # ascending order
   1575       results = base_query |> order_by(selected_as(:min_visits)) |> TestRepo.all()
   1576 
   1577       assert [%{posted: ~D[2020-12-20], min_visits: 0}, %{posted: ~D[2020-12-21], min_visits: 2}] =
   1578                results
   1579 
   1580       # descending order
   1581       results = base_query |> order_by([desc: selected_as(:min_visits)]) |> TestRepo.all()
   1582 
   1583       assert [%{posted: ~D[2020-12-21], min_visits: 2}, %{posted: ~D[2020-12-20], min_visits: 0}] =
   1584                results
   1585     end
   1586 
   1587     @tag :selected_as_with_order_by
   1588     test "selected_as/2 respects custom types" do
   1589       TestRepo.insert!(%Post{title: "title1", visits: 1})
   1590       TestRepo.insert!(%Post{title: "title2"})
   1591       uuid = Ecto.UUID.generate()
   1592 
   1593       query =
   1594         from p in Post,
   1595           select: %{
   1596             uuid: type(^uuid, Ecto.UUID) |> selected_as(:uuid),
   1597             visits: p.visits |> coalesce(0) |> selected_as(:visits)
   1598           },
   1599           order_by: [selected_as(:uuid), selected_as(:visits)]
   1600 
   1601       assert [%{uuid: ^uuid, visits: 0}, %{uuid: ^uuid, visits: 1}] = TestRepo.all(query)
   1602     end
   1603 
   1604     @tag :selected_as_with_order_by_expression
   1605     test "selected_as/2 with order_by expression" do
   1606       TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 3, intensity: 2.0})
   1607       TestRepo.insert!(%Post{posted: ~D[2020-12-20], visits: nil, intensity: 10.0})
   1608 
   1609       results =
   1610         from(p in Post,
   1611           select: %{
   1612             posted: p.posted,
   1613             visits: p.visits |> coalesce(0) |> selected_as(:num_visits),
   1614             intensity: selected_as(p.intensity, :strength)
   1615           },
   1616           order_by: [desc: (selected_as(:num_visits) + selected_as(:strength))]
   1617         )
   1618         |> TestRepo.all()
   1619 
   1620       assert [%{posted: ~D[2020-12-20], visits: 0}, %{posted: ~D[2020-12-21], visits: 3}] =
   1621                results
   1622     end
   1623 
   1624     @tag :selected_as_with_having
   1625     test "selected_as/2 with having" do
   1626       TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 3})
   1627       TestRepo.insert!(%Post{posted: ~D[2020-12-21], visits: 2})
   1628       TestRepo.insert!(%Post{posted: ~D[2020-12-20], visits: nil})
   1629 
   1630       results =
   1631         from(p in Post,
   1632           select: %{
   1633             posted: p.posted,
   1634             min_visits: p.visits |> coalesce(0) |> min() |> selected_as(:min_visits)
   1635           },
   1636           group_by: p.posted,
   1637           having: selected_as(:min_visits) > 0,
   1638           or_having: not(selected_as(:min_visits) > 0),
   1639           order_by: p.posted
   1640         )
   1641         |> TestRepo.all()
   1642 
   1643       assert [%{posted: ~D[2020-12-20], min_visits: 0}, %{posted: ~D[2020-12-21], min_visits: 2}] = results
   1644     end
   1645   end
   1646 
   1647   test "query count distinct" do
   1648     TestRepo.insert!(%Post{title: "1"})
   1649     TestRepo.insert!(%Post{title: "1"})
   1650     TestRepo.insert!(%Post{title: "2"})
   1651 
   1652     assert [3] == Post |> select([p], count(p.title)) |> TestRepo.all
   1653     assert [2] == Post |> select([p], count(p.title, :distinct)) |> TestRepo.all
   1654   end
   1655 
   1656   test "query where interpolation" do
   1657     post1 = TestRepo.insert!(%Post{title: "hello"})
   1658     post2 = TestRepo.insert!(%Post{title: "goodbye"})
   1659 
   1660     assert [post1, post2] == Post |> where([], []) |> TestRepo.all |> Enum.sort_by(& &1.id)
   1661     assert [post1]        == Post |> where([], [title: "hello"]) |> TestRepo.all
   1662     assert [post1]        == Post |> where([], [title: "hello", id: ^post1.id]) |> TestRepo.all
   1663 
   1664     params0 = []
   1665     params1 = [title: "hello"]
   1666     params2 = [title: "hello", id: post1.id]
   1667     assert [post1, post2]  == (from Post, where: ^params0) |> TestRepo.all |> Enum.sort_by(& &1.id)
   1668     assert [post1]         == (from Post, where: ^params1) |> TestRepo.all
   1669     assert [post1]         == (from Post, where: ^params2) |> TestRepo.all
   1670 
   1671     post3 = TestRepo.insert!(%Post{title: "goodbye", uuid: nil})
   1672     params3 = [title: "goodbye", uuid: post3.uuid]
   1673     assert [post3] == (from Post, where: ^params3) |> TestRepo.all
   1674   end
   1675 
   1676   describe "upsert via insert" do
   1677     @describetag :upsert
   1678 
   1679     test "on conflict raise" do
   1680       {:ok, inserted} = TestRepo.insert(%Post{title: "first"}, on_conflict: :raise)
   1681       assert catch_error(TestRepo.insert(%Post{id: inserted.id, title: "second"}, on_conflict: :raise))
   1682     end
   1683 
   1684     test "on conflict ignore" do
   1685       post = %Post{title: "first", uuid: Ecto.UUID.generate()}
   1686       {:ok, inserted} = TestRepo.insert(post, on_conflict: :nothing)
   1687       assert inserted.id
   1688       assert inserted.__meta__.state == :loaded
   1689 
   1690       {:ok, not_inserted} = TestRepo.insert(post, on_conflict: :nothing)
   1691       assert not_inserted.id == nil
   1692       assert not_inserted.__meta__.state == :loaded
   1693     end
   1694 
   1695     @tag :with_conflict_target
   1696     test "on conflict and associations" do
   1697       on_conflict = [set: [title: "second"]]
   1698       post = %Post{uuid: Ecto.UUID.generate(),
   1699                    title: "first", comments: [%Comment{}]}
   1700       {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
   1701       assert inserted.id
   1702     end
   1703 
   1704     @tag :with_conflict_target
   1705     test "on conflict with inc" do
   1706       uuid = "6fa459ea-ee8a-3ca4-894e-db77e160355e"
   1707       post = %Post{title: "first", uuid: uuid}
   1708       {:ok, _} = TestRepo.insert(post)
   1709       post = %{title: "upsert", uuid: uuid}
   1710       TestRepo.insert_all(Post, [post], on_conflict: [inc: [visits: 1]], conflict_target: :uuid)
   1711     end
   1712 
   1713     @tag :with_conflict_target
   1714     test "on conflict ignore and conflict target" do
   1715       post = %Post{title: "first", uuid: Ecto.UUID.generate()}
   1716       {:ok, inserted} = TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:uuid])
   1717       assert inserted.id
   1718 
   1719       # Error on non-conflict target
   1720       assert catch_error(TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:id]))
   1721 
   1722       # Error on conflict target
   1723       {:ok, not_inserted} = TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:uuid])
   1724       assert not_inserted.id == nil
   1725     end
   1726 
   1727     @tag :without_conflict_target
   1728     test "on conflict keyword list" do
   1729       on_conflict = [set: [title: "second"]]
   1730       post = %Post{title: "first", uuid: Ecto.UUID.generate()}
   1731       {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict)
   1732       assert inserted.id
   1733 
   1734       {:ok, updated} = TestRepo.insert(post, on_conflict: on_conflict)
   1735       assert updated.id == inserted.id
   1736       assert updated.title != "second"
   1737       assert TestRepo.get!(Post, inserted.id).title == "second"
   1738     end
   1739 
   1740     @tag :with_conflict_target
   1741     test "on conflict keyword list and conflict target" do
   1742       on_conflict = [set: [title: "second"]]
   1743       post = %Post{title: "first", uuid: Ecto.UUID.generate()}
   1744       {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
   1745       assert inserted.id
   1746 
   1747       # Error on non-conflict target
   1748       assert catch_error(TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:id]))
   1749 
   1750       {:ok, updated} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
   1751       assert updated.id == inserted.id
   1752       assert updated.title != "second"
   1753       assert TestRepo.get!(Post, inserted.id).title == "second"
   1754     end
   1755 
   1756     @tag :returning
   1757     @tag :with_conflict_target
   1758     test "on conflict keyword list and conflict target and returning" do
   1759       {:ok, c1} = TestRepo.insert(%Post{})
   1760       {:ok, c2} = TestRepo.insert(%Post{id: c1.id}, on_conflict: [set: [id: c1.id]], conflict_target: [:id], returning: [:id, :uuid])
   1761       {:ok, c3} = TestRepo.insert(%Post{id: c1.id}, on_conflict: [set: [id: c1.id]], conflict_target: [:id], returning: true)
   1762       {:ok, c4} = TestRepo.insert(%Post{id: c1.id}, on_conflict: [set: [id: c1.id]], conflict_target: [:id], returning: false)
   1763 
   1764       assert c2.uuid == c1.uuid
   1765       assert c3.uuid == c1.uuid
   1766       assert c4.uuid != c1.uuid
   1767     end
   1768 
   1769     @tag :returning
   1770     @tag :with_conflict_target
   1771     test "on conflict keyword list and conflict target and returning and field source" do
   1772       TestRepo.insert!(%Permalink{url: "old"})
   1773       {:ok, c1} = TestRepo.insert(%Permalink{url: "old"},
   1774                                   on_conflict: [set: [url: "new1"]],
   1775                                   conflict_target: [:url],
   1776                                   returning: [:url])
   1777 
   1778       TestRepo.insert!(%Permalink{url: "old"})
   1779       {:ok, c2} = TestRepo.insert(%Permalink{url: "old"},
   1780                                   on_conflict: [set: [url: "new2"]],
   1781                                   conflict_target: [:url],
   1782                                   returning: true)
   1783 
   1784       assert c1.url == "new1"
   1785       assert c2.url == "new2"
   1786     end
   1787 
   1788     @tag :returning
   1789     @tag :with_conflict_target
   1790     test "on conflict ignore and returning" do
   1791       post = %Post{title: "first", uuid: Ecto.UUID.generate()}
   1792       {:ok, inserted} = TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:uuid])
   1793       assert inserted.id
   1794 
   1795       {:ok, not_inserted} = TestRepo.insert(post, on_conflict: :nothing, conflict_target: [:uuid], returning: true)
   1796       assert not_inserted.id == nil
   1797     end
   1798 
   1799     @tag :without_conflict_target
   1800     test "on conflict query" do
   1801       on_conflict = from Post, update: [set: [title: "second"]]
   1802       post = %Post{title: "first", uuid: Ecto.UUID.generate()}
   1803       {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict)
   1804       assert inserted.id
   1805 
   1806       {:ok, updated} = TestRepo.insert(post, on_conflict: on_conflict)
   1807       assert updated.id == inserted.id
   1808       assert updated.title != "second"
   1809       assert TestRepo.get!(Post, inserted.id).title == "second"
   1810     end
   1811 
   1812     @tag :with_conflict_target
   1813     test "on conflict query and conflict target" do
   1814       on_conflict = from Post, update: [set: [title: "second"]]
   1815       post = %Post{title: "first", uuid: Ecto.UUID.generate()}
   1816       {:ok, inserted} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
   1817       assert inserted.id
   1818 
   1819       # Error on non-conflict target
   1820       assert catch_error(TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:id]))
   1821 
   1822       {:ok, updated} = TestRepo.insert(post, on_conflict: on_conflict, conflict_target: [:uuid])
   1823       assert updated.id == inserted.id
   1824       assert updated.title != "second"
   1825       assert TestRepo.get!(Post, inserted.id).title == "second"
   1826     end
   1827 
   1828     @tag :with_conflict_target
   1829     test "on conflict query having condition" do
   1830       post = %Post{title: "first", counter: 1, uuid: Ecto.UUID.generate()}
   1831       {:ok, inserted} = TestRepo.insert(post)
   1832 
   1833       on_conflict = from Post, where: [counter: 2], update: [set: [title: "second"]]
   1834 
   1835       insert_options = [
   1836         on_conflict: on_conflict,
   1837         conflict_target: [:uuid],
   1838         stale_error_field: :counter
   1839       ]
   1840 
   1841       assert {:error, changeset} = TestRepo.insert(post, insert_options)
   1842       assert changeset.errors == [counter: {"is stale", [stale: true]}]
   1843 
   1844       assert TestRepo.get!(Post, inserted.id).title == "first"
   1845     end
   1846 
   1847     @tag :without_conflict_target
   1848     test "on conflict replace_all" do
   1849       post = %Post{title: "first", visits: 13, uuid: Ecto.UUID.generate()}
   1850       {:ok, inserted} = TestRepo.insert(post, on_conflict: :replace_all)
   1851       assert inserted.id
   1852 
   1853       post = %Post{title: "updated", visits: 17, uuid: post.uuid}
   1854       post = TestRepo.insert!(post, on_conflict: :replace_all)
   1855       assert post.id != inserted.id
   1856       assert post.title == "updated"
   1857       assert post.visits == 17
   1858 
   1859       assert TestRepo.all(from p in Post, select: {p.id, p.title, p.visits}) ==
   1860              [{post.id, "updated", 17}]
   1861       assert TestRepo.all(from p in Post, select: count(p.id)) == [1]
   1862     end
   1863 
   1864     @tag :with_conflict_target
   1865     test "on conflict replace_all and conflict target" do
   1866       post = %Post{title: "first", visits: 13, uuid: Ecto.UUID.generate()}
   1867       {:ok, inserted} = TestRepo.insert(post, on_conflict: :replace_all, conflict_target: :uuid)
   1868       assert inserted.id
   1869 
   1870       post = %Post{title: "updated", visits: 17, uuid: post.uuid}
   1871       post = TestRepo.insert!(post, on_conflict: :replace_all, conflict_target: :uuid)
   1872       assert post.id != inserted.id
   1873       assert post.title == "updated"
   1874       assert post.visits == 17
   1875 
   1876       assert TestRepo.all(from p in Post, select: {p.id, p.title, p.visits}) ==
   1877              [{post.id, "updated", 17}]
   1878       assert TestRepo.all(from p in Post, select: count(p.id)) == [1]
   1879     end
   1880   end
   1881 
   1882   describe "upsert via insert_all" do
   1883     @describetag :upsert_all
   1884 
   1885     test "on conflict raise" do
   1886       post = [title: "first", uuid: Ecto.UUID.generate()]
   1887       {1, nil} = TestRepo.insert_all(Post, [post], on_conflict: :raise)
   1888       assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: :raise))
   1889     end
   1890 
   1891     test "on conflict ignore" do
   1892       post = [title: "first", uuid: Ecto.UUID.generate()]
   1893       assert TestRepo.insert_all(Post, [post], on_conflict: :nothing) == {1, nil}
   1894 
   1895       # PG returns 0, MySQL returns 1
   1896       {entries, nil} = TestRepo.insert_all(Post, [post], on_conflict: :nothing)
   1897       assert entries == 0 or entries == 1
   1898 
   1899       assert length(TestRepo.all(Post)) == 1
   1900     end
   1901 
   1902     @tag :with_conflict_target
   1903     test "on conflict ignore and conflict target" do
   1904       post = [title: "first", uuid: Ecto.UUID.generate()]
   1905       assert TestRepo.insert_all(Post, [post], on_conflict: :nothing, conflict_target: [:uuid]) ==
   1906              {1, nil}
   1907 
   1908       # Error on non-conflict target
   1909       assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: :nothing, conflict_target: [:id]))
   1910 
   1911       # Error on conflict target
   1912       assert TestRepo.insert_all(Post, [post], on_conflict: :nothing, conflict_target: [:uuid]) ==
   1913              {0, nil}
   1914     end
   1915 
   1916     @tag :with_conflict_target
   1917     test "on conflict keyword list and conflict target" do
   1918       on_conflict = [set: [title: "second"]]
   1919       post = [title: "first", uuid: Ecto.UUID.generate()]
   1920       {1, nil} = TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid])
   1921 
   1922       # Error on non-conflict target
   1923       assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:id]))
   1924 
   1925       # Error on conflict target
   1926       assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
   1927              {1, nil}
   1928       assert TestRepo.all(from p in Post, select: p.title) == ["second"]
   1929     end
   1930 
   1931     @tag :with_conflict_target
   1932     @tag :returning
   1933     test "on conflict keyword list and conflict target and returning and source field" do
   1934       on_conflict = [set: [url: "new"]]
   1935       permalink = [url: "old"]
   1936 
   1937       assert {1, [%Permalink{url: "old"}]} =
   1938              TestRepo.insert_all(Permalink, [permalink],
   1939                                  on_conflict: on_conflict, conflict_target: [:url], returning: [:url])
   1940 
   1941       assert {1, [%Permalink{url: "new"}]} =
   1942              TestRepo.insert_all(Permalink, [permalink],
   1943                                  on_conflict: on_conflict, conflict_target: [:url], returning: [:url])
   1944     end
   1945 
   1946     @tag :with_conflict_target
   1947     test "on conflict query and conflict target" do
   1948       on_conflict = from p in Post, where: p.id > ^0, update: [set: [title: "second"]]
   1949       post = [title: "first", uuid: Ecto.UUID.generate()]
   1950       assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
   1951              {1, nil}
   1952 
   1953       # Error on non-conflict target
   1954       assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:id]))
   1955 
   1956       # Error on conflict target
   1957       assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
   1958              {1, nil}
   1959       assert TestRepo.all(from p in Post, select: p.title) == ["second"]
   1960     end
   1961 
   1962     @tag :insert_select
   1963     @tag :with_conflict_target
   1964     test "on conflict query and insert select and conflict target" do
   1965       on_conflict = from p in Post, where: p.id > ^0, update: [set: [title: "second"]]
   1966       visits_value = from p in Post, where: p.public == ^true and p.id > ^0, select: p.visits, limit: 1
   1967       post = [title: "first", uuid: Ecto.UUID.generate(), visits: visits_value]
   1968       assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
   1969              {1, nil}
   1970 
   1971       # Error on non-conflict target
   1972       assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:id]))
   1973 
   1974       # Error on conflict target
   1975       assert TestRepo.insert_all(Post, [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
   1976              {1, nil}
   1977       assert TestRepo.all(from p in Post, select: p.title) == ["second"]
   1978     end
   1979 
   1980     @tag :returning
   1981     @tag :with_conflict_target
   1982     test "on conflict query and conflict target and returning" do
   1983       on_conflict = from Post, update: [set: [title: "second"]]
   1984       post = [title: "first", uuid: Ecto.UUID.generate()]
   1985       {1, [%{id: id}]} = TestRepo.insert_all(Post, [post], on_conflict: on_conflict,
   1986                                             conflict_target: [:uuid], returning: [:id])
   1987 
   1988       # Error on non-conflict target
   1989       assert catch_error(TestRepo.insert_all(Post, [post], on_conflict: on_conflict,
   1990                                              conflict_target: [:id], returning: [:id]))
   1991 
   1992       # Error on conflict target
   1993       {1, [%Post{id: ^id, title: "second"}]} =
   1994         TestRepo.insert_all(Post, [post], on_conflict: on_conflict,
   1995                             conflict_target: [:uuid], returning: [:id, :title])
   1996     end
   1997 
   1998     @tag :with_conflict_target
   1999     test "source (without an Ecto schema) on conflict query and conflict target" do
   2000       on_conflict = [set: [title: "second"]]
   2001       {:ok, uuid} = Ecto.UUID.dump(Ecto.UUID.generate())
   2002       post = [title: "first", uuid: uuid]
   2003       assert TestRepo.insert_all("posts", [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
   2004              {1, nil}
   2005 
   2006       # Error on non-conflict target
   2007       assert catch_error(TestRepo.insert_all("posts", [post], on_conflict: on_conflict, conflict_target: [:id]))
   2008 
   2009       # Error on conflict target
   2010       assert TestRepo.insert_all("posts", [post], on_conflict: on_conflict, conflict_target: [:uuid]) ==
   2011              {1, nil}
   2012       assert TestRepo.all(from p in Post, select: p.title) == ["second"]
   2013     end
   2014 
   2015     @tag :without_conflict_target
   2016     test "on conflict replace_all" do
   2017       post_first = %Post{title: "first", public: true, uuid: Ecto.UUID.generate()}
   2018       post_second = %Post{title: "second", public: false, uuid: Ecto.UUID.generate()}
   2019 
   2020       {:ok, post_first} = TestRepo.insert(post_first, on_conflict: :replace_all)
   2021       {:ok, post_second} = TestRepo.insert(post_second, on_conflict: :replace_all)
   2022 
   2023       assert post_first.id
   2024       assert post_second.id
   2025       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2026 
   2027       # Multiple record change value: note IDS are also replaced
   2028       changes = [%{id: post_first.id + 2, title: "first_updated",
   2029                    visits: 1, uuid: post_first.uuid},
   2030                  %{id: post_second.id + 2, title: "second_updated",
   2031                    visits: 2, uuid: post_second.uuid}]
   2032 
   2033       TestRepo.insert_all(Post, changes, on_conflict: :replace_all)
   2034       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2035 
   2036       updated_first = TestRepo.get(Post, post_first.id + 2)
   2037       assert updated_first.title == "first_updated"
   2038       assert updated_first.visits == 1
   2039 
   2040       updated_second = TestRepo.get(Post, post_second.id + 2)
   2041       assert updated_second.title == "second_updated"
   2042       assert updated_second.visits == 2
   2043     end
   2044 
   2045     @tag :with_conflict_target
   2046     test "on conflict replace_all and conflict_target" do
   2047       post_first = %Post{title: "first", public: true, uuid: Ecto.UUID.generate()}
   2048       post_second = %Post{title: "second", public: false, uuid: Ecto.UUID.generate()}
   2049 
   2050       {:ok, post_first} = TestRepo.insert(post_first, on_conflict: :replace_all, conflict_target: :uuid)
   2051       {:ok, post_second} = TestRepo.insert(post_second, on_conflict: :replace_all, conflict_target: :uuid)
   2052 
   2053       assert post_first.id
   2054       assert post_second.id
   2055       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2056 
   2057       # Multiple record change value: note IDS are also replaced
   2058       changes = [%{id: post_second.id + 1, title: "first_updated",
   2059                    visits: 1, uuid: post_first.uuid},
   2060                  %{id: post_second.id + 2, title: "second_updated",
   2061                    visits: 2, uuid: post_second.uuid}]
   2062 
   2063       TestRepo.insert_all(Post, changes, on_conflict: :replace_all, conflict_target: :uuid)
   2064       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2065 
   2066       updated_first = TestRepo.get(Post, post_second.id + 1)
   2067       assert updated_first.title == "first_updated"
   2068       assert updated_first.visits == 1
   2069 
   2070       updated_second = TestRepo.get(Post, post_second.id + 2)
   2071       assert updated_second.title == "second_updated"
   2072       assert updated_second.visits == 2
   2073     end
   2074 
   2075     @tag :without_conflict_target
   2076     test "on conflict replace_all_except" do
   2077       post_first = %Post{title: "first", public: true, uuid: Ecto.UUID.generate()}
   2078       post_second = %Post{title: "second", public: false, uuid: Ecto.UUID.generate()}
   2079 
   2080       {:ok, post_first} = TestRepo.insert(post_first, on_conflict: {:replace_all_except, [:id]})
   2081       {:ok, post_second} = TestRepo.insert(post_second, on_conflict: {:replace_all_except, [:id]})
   2082 
   2083       assert post_first.id
   2084       assert post_second.id
   2085       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2086 
   2087       # Multiple record change value: note IDS are not replaced
   2088       changes = [%{id: post_first.id + 2, title: "first_updated",
   2089                    visits: 1, uuid: post_first.uuid},
   2090                  %{id: post_second.id + 2, title: "second_updated",
   2091                    visits: 2, uuid: post_second.uuid}]
   2092 
   2093       TestRepo.insert_all(Post, changes, on_conflict: {:replace_all_except, [:id]})
   2094       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2095 
   2096       updated_first = TestRepo.get(Post, post_first.id)
   2097       assert updated_first.title == "first_updated"
   2098       assert updated_first.visits == 1
   2099 
   2100       updated_second = TestRepo.get(Post, post_second.id)
   2101       assert updated_second.title == "second_updated"
   2102       assert updated_second.visits == 2
   2103     end
   2104 
   2105     @tag :with_conflict_target
   2106     test "on conflict replace_all_except and conflict_target" do
   2107       post_first = %Post{title: "first", public: true, uuid: Ecto.UUID.generate()}
   2108       post_second = %Post{title: "second", public: false, uuid: Ecto.UUID.generate()}
   2109 
   2110       {:ok, post_first} = TestRepo.insert(post_first, on_conflict: {:replace_all_except, [:id]}, conflict_target: :uuid)
   2111       {:ok, post_second} = TestRepo.insert(post_second, on_conflict: {:replace_all_except, [:id]}, conflict_target: :uuid)
   2112 
   2113       assert post_first.id
   2114       assert post_second.id
   2115       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2116 
   2117       # Multiple record change value: note IDS are not replaced
   2118       changes = [%{id: post_first.id + 2, title: "first_updated",
   2119                    visits: 1, uuid: post_first.uuid},
   2120                  %{id: post_second.id + 2, title: "second_updated",
   2121                    visits: 2, uuid: post_second.uuid}]
   2122 
   2123       TestRepo.insert_all(Post, changes, on_conflict: {:replace_all_except, [:id]}, conflict_target: :uuid)
   2124       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2125 
   2126       updated_first = TestRepo.get(Post, post_first.id)
   2127       assert updated_first.title == "first_updated"
   2128       assert updated_first.visits == 1
   2129 
   2130       updated_second = TestRepo.get(Post, post_second.id)
   2131       assert updated_second.title == "second_updated"
   2132       assert updated_second.visits == 2
   2133     end
   2134 
   2135     @tag :with_conflict_target
   2136     test "on conflict replace and conflict_target" do
   2137       post_first = %Post{title: "first", visits: 10, public: true, uuid: Ecto.UUID.generate()}
   2138       post_second = %Post{title: "second", visits: 20, public: false, uuid: Ecto.UUID.generate()}
   2139 
   2140       {:ok, post_first} = TestRepo.insert(post_first, on_conflict: {:replace, [:title, :visits]}, conflict_target: :uuid)
   2141       {:ok, post_second} = TestRepo.insert(post_second, on_conflict: {:replace, [:title, :visits]}, conflict_target: :uuid)
   2142 
   2143       assert post_first.id
   2144       assert post_second.id
   2145       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2146 
   2147       # Multiple record change value: note `public` field is not changed
   2148       changes = [%{id: post_first.id, title: "first_updated", visits: 11, public: false, uuid: post_first.uuid},
   2149                  %{id: post_second.id, title: "second_updated", visits: 21, public: true, uuid: post_second.uuid}]
   2150 
   2151       TestRepo.insert_all(Post, changes, on_conflict: {:replace, [:title, :visits]}, conflict_target: :uuid)
   2152       assert TestRepo.all(from p in Post, select: count(p.id)) == [2]
   2153 
   2154       updated_first = TestRepo.get(Post, post_first.id)
   2155       assert updated_first.title == "first_updated"
   2156       assert updated_first.visits == 11
   2157       assert updated_first.public == true
   2158 
   2159       updated_second = TestRepo.get(Post, post_second.id)
   2160       assert updated_second.title == "second_updated"
   2161       assert updated_second.visits == 21
   2162       assert updated_second.public == false
   2163     end
   2164   end
   2165 end