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