ash_postgres/test/load_test.exs
2024-05-16 17:28:43 -05:00

675 lines
20 KiB
Elixir

defmodule AshPostgres.Test.LoadTest do
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Author, Comment, Post, Record, TempEntity, User}
require Ash.Query
test "has_many relationships can be loaded" do
assert %Post{comments: %Ash.NotLoaded{type: :relationship}} =
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Ash.create!()
results =
Post
|> Ash.Query.load(:comments)
|> Ash.read!()
assert [%Post{comments: [%{title: "match"}]}] = results
end
test "belongs_to relationships can be loaded" do
assert %Comment{post: %Ash.NotLoaded{type: :relationship}} =
comment =
Comment
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:comments, [comment], type: :append_and_remove)
|> Ash.create!()
results =
Comment
|> Ash.Query.load(:post)
|> Ash.read!()
assert [%Comment{post: %{title: "match"}}] = results
end
test "many_to_many loads work" do
source_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "source"})
|> Ash.create!()
destination_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "destination"})
|> Ash.create!()
destination_post2 =
Post
|> Ash.Changeset.for_create(:create, %{title: "destination"})
|> Ash.create!()
source_post
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2],
type: :append_and_remove
)
|> Ash.update!()
results =
source_post
|> Ash.load!(:linked_posts)
assert %{linked_posts: [%{title: "destination"}, %{title: "destination"}]} = results
end
test "many_to_many loads work when nested" do
source_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "source"})
|> Ash.create!()
destination_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "destination"})
|> Ash.create!()
source_post
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post],
type: :append_and_remove
)
|> Ash.update!()
destination_post
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [source_post], type: :append_and_remove)
|> Ash.update!()
results =
source_post
|> Ash.load!(linked_posts: :linked_posts)
assert %{linked_posts: [%{title: "destination", linked_posts: [%{title: "source"}]}]} =
results
end
describe "lateral join loads" do
test "parent references are resolved" do
post1 =
Post
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
post2 =
Post
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
post2_id = post2.id
post3 =
Post
|> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.create!()
assert [%{posts_with_matching_title: [%{id: ^post2_id}]}] =
Post
|> Ash.Query.load(:posts_with_matching_title)
|> Ash.Query.filter(id == ^post1.id)
|> Ash.read!()
assert [%{posts_with_matching_title: []}] =
Post
|> Ash.Query.load(:posts_with_matching_title)
|> Ash.Query.filter(id == ^post3.id)
|> Ash.read!()
end
test "parent references work when joining for filters" do
%{id: post1_id} =
Post
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
post2 =
Post
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.create!()
assert [%{id: ^post1_id}] =
Post
|> Ash.Query.filter(posts_with_matching_title.id == ^post2.id)
|> Ash.read!()
end
test "lateral join loads (loads with limits or offsets) are supported" do
assert %Post{comments: %Ash.NotLoaded{type: :relationship}} =
post =
Post
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.for_create(:create, %{title: "abc"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Ash.create!()
Comment
|> Ash.Changeset.for_create(:create, %{title: "def"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Ash.create!()
comments_query =
Comment
|> Ash.Query.limit(1)
|> Ash.Query.sort(:title)
results =
Post
|> Ash.Query.load(comments: comments_query)
|> Ash.read!()
assert [%Post{comments: [%{title: "abc"}]}] = results
comments_query =
Comment
|> Ash.Query.limit(1)
|> Ash.Query.sort(title: :desc)
results =
Post
|> Ash.Query.load(comments: comments_query)
|> Ash.read!()
assert [%Post{comments: [%{title: "def"}]}] = results
comments_query =
Comment
|> Ash.Query.limit(2)
|> Ash.Query.sort(title: :desc)
results =
Post
|> Ash.Query.load(comments: comments_query)
|> Ash.read!()
assert [%Post{comments: [%{title: "def"}, %{title: "abc"}]}] = results
end
test "loading many to many relationships on records works without loading its join relationship when using code interface" do
source_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "source"})
|> Ash.create!()
destination_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "abc"})
|> Ash.create!()
destination_post2 =
Post
|> Ash.Changeset.for_create(:create, %{title: "def"})
|> Ash.create!()
source_post
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2],
type: :append_and_remove
)
|> Ash.update!()
assert %{linked_posts: [_, _]} = Post.get_by_id!(source_post.id, load: [:linked_posts])
end
test "lateral join loads with many to many relationships are supported" do
source_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "source"})
|> Ash.create!()
destination_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "abc"})
|> Ash.create!()
destination_post2 =
Post
|> Ash.Changeset.for_create(:create, %{title: "def"})
|> Ash.create!()
source_post
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2],
type: :append_and_remove
)
|> Ash.update!()
linked_posts_query =
Post
|> Ash.Query.limit(1)
|> Ash.Query.sort(title: :asc)
results =
source_post
|> Ash.load!(linked_posts: linked_posts_query)
assert %{linked_posts: [%{title: "abc"}]} = results
linked_posts_query =
Post
|> Ash.Query.limit(2)
|> Ash.Query.sort(title: :asc)
results =
source_post
|> Ash.load!(linked_posts: linked_posts_query)
assert %{linked_posts: [%{title: "abc"}, %{title: "def"}]} = results
end
test "lateral join loads with many to many relationships are supported with aggregates" do
source_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "source"})
|> Ash.create!()
destination_post =
Post
|> Ash.Changeset.for_create(:create, %{title: "abc"})
|> Ash.create!()
destination_post2 =
Post
|> Ash.Changeset.for_create(:create, %{title: "def"})
|> Ash.create!()
source_post
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2],
type: :append_and_remove
)
|> Ash.update!()
linked_posts_query =
Post
|> Ash.Query.limit(1)
|> Ash.Query.sort(title: :asc)
results =
source_post
|> Ash.load!(linked_posts: linked_posts_query)
assert %{linked_posts: [%{title: "abc"}]} = results
linked_posts_query =
Post
|> Ash.Query.limit(2)
|> Ash.Query.sort(title: :asc)
|> Ash.Query.filter(count_of_comments_called_match == 0)
results =
source_post
|> Ash.load!(linked_posts: linked_posts_query)
assert %{linked_posts: [%{title: "abc"}, %{title: "def"}]} = results
end
test "lateral join loads with read action from a custom table and schema" do
record = Record |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!()
temp_entity =
TempEntity |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!()
assert %{entity: entity} = Ash.load!(record, :entity)
assert temp_entity.id == entity.id
end
end
describe "relationship pagination" do
test "it allows paginating has_many relationships with offset pagination" do
author1 =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "a"})
|> Ash.create!()
author2 =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "b"})
|> Ash.create!()
for i <- 0..9 do
Post
|> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id})
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{title: "author2 post#{i}", author_id: author2.id})
|> Ash.create!()
end
paginated_posts =
Post
|> Ash.Query.for_read(:paginated)
|> Ash.Query.page(limit: 2, offset: 2)
|> Ash.Query.sort(:title)
assert [author1, author2] =
Author
|> Ash.Query.sort(:first_name)
|> Ash.Query.load(posts: paginated_posts)
|> Ash.read!()
assert %Ash.Page.Offset{
results: [%{title: "author1 post2"}, %{title: "author1 post3"}]
} = author1.posts
assert %Ash.Page.Offset{
results: [%{title: "author2 post2"}, %{title: "author2 post3"}]
} = author2.posts
assert %Ash.Page.Offset{
results: [%{title: "author1 post4"}, %{title: "author1 post5"}]
} = Ash.page!(author1.posts, :next)
end
test "it allows paginating has_many relationships with keyset pagination" do
author1 =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "a"})
|> Ash.create!()
author2 =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "b"})
|> Ash.create!()
for i <- 0..9 do
Post
|> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id})
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{title: "author2 post#{i}", author_id: author2.id})
|> Ash.create!()
end
paginated_posts =
Post
|> Ash.Query.for_read(:keyset)
|> Ash.Query.page(limit: 2)
|> Ash.Query.sort(:title)
assert [author1, author2] =
Author
|> Ash.Query.sort(:first_name)
|> Ash.Query.load(posts: paginated_posts)
|> Ash.read!()
assert %Ash.Page.Keyset{
results: [%{title: "author1 post0"}, %{title: "author1 post1"}]
} = author1.posts
assert %Ash.Page.Keyset{
results: [%{title: "author2 post0"}, %{title: "author2 post1"}]
} = author2.posts
assert %Ash.Page.Keyset{
results: [%{title: "author1 post2"}, %{title: "author1 post3"}]
} = Ash.page!(author1.posts, :next)
end
test "it allows paginating many_to_many relationships with offset pagination" do
followers =
for i <- 0..9 do
User
|> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true})
|> Ash.create!()
end
followers_0_to_6 = Enum.take(followers, 6)
followers_5_to_9 = Enum.slice(followers, 5..9)
Post
|> Ash.Changeset.for_create(:create, %{title: "a"})
|> Ash.Changeset.manage_relationship(:followers, followers_0_to_6, type: :append_and_remove)
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{title: "b"})
|> Ash.Changeset.manage_relationship(:followers, followers_5_to_9, type: :append_and_remove)
|> Ash.create!()
paginated_followers =
User
|> Ash.Query.page(limit: 2)
|> Ash.Query.sort(:name)
assert [post1, post2] =
Post
|> Ash.Query.sort(:title)
|> Ash.Query.load(followers: paginated_followers)
|> Ash.read!()
assert %Ash.Page.Offset{
results: [%{name: "user0"}, %{name: "user1"}]
} = post1.followers
assert %Ash.Page.Offset{
results: [%{name: "user5"}, %{name: "user6"}]
} = post2.followers
assert %Ash.Page.Offset{
results: [%{name: "user2"}, %{name: "user3"}]
} = Ash.page!(post1.followers, :next)
end
test "it allows paginating many_to_many relationships with keyset pagination" do
followers =
for i <- 0..9 do
User
|> Ash.Changeset.for_create(:create, %{name: "user#{i}"})
|> Ash.create!()
end
followers_0_to_6 = Enum.take(followers, 6)
followers_5_to_9 = Enum.slice(followers, 5..9)
Post
|> Ash.Changeset.for_create(:create, %{title: "a"})
|> Ash.Changeset.manage_relationship(:followers, followers_0_to_6, type: :append_and_remove)
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{title: "b"})
|> Ash.Changeset.manage_relationship(:followers, followers_5_to_9, type: :append_and_remove)
|> Ash.create!()
paginated_followers =
User
|> Ash.Query.for_read(:keyset)
|> Ash.Query.page(limit: 2)
|> Ash.Query.sort(:name)
assert [post1, post2] =
Post
|> Ash.Query.sort(:title)
|> Ash.Query.load(followers: paginated_followers)
|> Ash.read!()
assert %Ash.Page.Keyset{
results: [%{name: "user0"}, %{name: "user1"}]
} = post1.followers
assert %Ash.Page.Keyset{
results: [%{name: "user5"}, %{name: "user6"}]
} = post2.followers
assert %Ash.Page.Keyset{
results: [%{name: "user2"}, %{name: "user3"}]
} = Ash.page!(post1.followers, :next)
end
test "works when nested with offset" do
author1 =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "a"})
|> Ash.create!()
author2 =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "b"})
|> Ash.create!()
followers =
for i <- 0..9 do
User
|> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true})
|> Ash.create!()
end
followers_0_to_6 = Enum.take(followers, 6)
followers_5_to_9 = Enum.slice(followers, 5..9)
for i <- 0..5 do
Post
|> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id})
|> Ash.Changeset.manage_relationship(:followers, followers_0_to_6,
type: :append_and_remove
)
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{title: "author2 post#{i}", author_id: author2.id})
|> Ash.Changeset.manage_relationship(:followers, followers_5_to_9,
type: :append_and_remove
)
|> Ash.create!()
end
paginated_followers =
User
|> Ash.Query.page(limit: 1)
|> Ash.Query.sort(:name)
paginated_posts =
Post
|> Ash.Query.for_read(:paginated)
|> Ash.Query.load(followers: paginated_followers)
|> Ash.Query.page(limit: 1)
|> Ash.Query.sort(:title)
assert %Ash.Page.Offset{results: [author1]} =
Author
|> Ash.Query.sort(:first_name)
|> Ash.Query.load(posts: paginated_posts)
|> Ash.read!(page: [limit: 1])
assert %Ash.Page.Offset{
results: [
%{
title: "author1 post0",
followers: %Ash.Page.Offset{results: [%{name: "user0"}]} = followers_page
}
]
} = author1.posts
assert %Ash.Page.Offset{results: [%{title: "author1 post1"}]} =
Ash.page!(author1.posts, :next)
assert %Ash.Page.Offset{results: [%{name: "user1"}]} = Ash.page!(followers_page, :next)
end
test "works when nested with keyset" do
author1 =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "a"})
|> Ash.create!()
author2 =
Author
|> Ash.Changeset.for_create(:create, %{first_name: "b"})
|> Ash.create!()
followers =
for i <- 0..9 do
User
|> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true})
|> Ash.create!()
end
followers_0_to_6 = Enum.take(followers, 6)
followers_5_to_9 = Enum.slice(followers, 5..9)
for i <- 0..5 do
Post
|> Ash.Changeset.for_create(:create, %{title: "author1 post#{i}", author_id: author1.id})
|> Ash.Changeset.manage_relationship(:followers, followers_0_to_6,
type: :append_and_remove
)
|> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{title: "author2 post#{i}", author_id: author2.id})
|> Ash.Changeset.manage_relationship(:followers, followers_5_to_9,
type: :append_and_remove
)
|> Ash.create!()
end
paginated_followers =
User
|> Ash.Query.for_read(:keyset)
|> Ash.Query.page(limit: 1)
|> Ash.Query.sort(:name)
paginated_posts =
Post
|> Ash.Query.for_read(:keyset)
|> Ash.Query.load(followers: paginated_followers)
|> Ash.Query.page(limit: 1)
|> Ash.Query.sort(:title)
assert [author1, _author2] =
Author
|> Ash.Query.sort(:first_name)
|> Ash.Query.load(posts: paginated_posts)
|> Ash.read!()
assert %Ash.Page.Keyset{
results: [
%{
title: "author1 post0",
followers: %Ash.Page.Keyset{results: [%{name: "user0"}]} = followers_page
}
]
} = author1.posts
assert %Ash.Page.Keyset{results: [%{title: "author1 post1"}]} =
Ash.page!(author1.posts, :next)
assert %Ash.Page.Keyset{results: [%{name: "user1"}]} = Ash.page!(followers_page, :next)
end
end
end