Skip to content

Commit 59d21ee

Browse files
Fix nil map associations (#4631)
1 parent 18a633a commit 59d21ee

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

integration_test/cases/preload.exs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,51 @@ defmodule Ecto.Integration.PreloadTest do
526526
assert [] = sort_by_id(p3.comments)
527527
end
528528

529+
test "take with join nil maps (many association)" do
530+
p = TestRepo.insert!(%Post{})
531+
532+
# many
533+
query =
534+
from p in Post,
535+
left_join: c in Comment,
536+
on: p.id == c.post_id,
537+
select: map(p, [:id, comments: [:id, :post_id]]),
538+
preload: [comments: c]
539+
540+
assert TestRepo.one(query) == %{id: p.id, comments: []}
541+
542+
query =
543+
from p in Post,
544+
left_join: c in Comment,
545+
on: p.id == c.post_id,
546+
select: map(p, [:id, comments: [:id, :post_id]]),
547+
preload: [:comments]
548+
549+
assert TestRepo.one(query) == %{id: p.id, comments: []}
550+
end
551+
552+
test "take with join nil maps (one association)" do
553+
p = TestRepo.insert!(%Post{})
554+
555+
query =
556+
from p in Post,
557+
left_join: u in User,
558+
on: p.author_id == u.id,
559+
select: map(p, [:id, author: [:id, :name]]),
560+
preload: [author: u]
561+
562+
assert TestRepo.one(query) == %{id: p.id, author: nil}
563+
564+
query =
565+
from p in Post,
566+
left_join: u in User,
567+
on: p.author_id == u.id,
568+
select: map(p, [:id, author: [:id, :name]]),
569+
preload: [:author]
570+
571+
assert TestRepo.one(query) == %{id: p.id, author: nil}
572+
end
573+
529574
test "preload through with take" do
530575
%Post{id: pid1} = TestRepo.insert!(%Post{})
531576

lib/ecto/repo/assoc.ex

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,24 @@ defmodule Ecto.Repo.Assoc do
3333
end
3434

3535
defp merge([struct|sub_structs], {primary_keys, cache, dict, sub_dicts}, parent_key) do
36-
child_key =
36+
{struct, child_key} =
3737
if struct do
38-
for primary_key <- primary_keys do
39-
case Map.get(struct, primary_key) do
40-
nil -> raise Ecto.NoPrimaryKeyValueError, struct: struct
41-
value -> value
42-
end
43-
end
38+
{child_key, all_nil?} =
39+
Enum.map_reduce(primary_keys, true, fn primary_key, all_nil? ->
40+
case struct do
41+
%_{^primary_key => nil} -> raise Ecto.NoPrimaryKeyValueError, struct: struct
42+
# We allow maps to be returned with all `nil` values in queries without
43+
# preloads. For preloads we have to treat maps with all `nil` values as
44+
# `nil` instead of a map otherwise we can't associate the missing
45+
# association to the parent struct
46+
%{^primary_key => value} -> {value, all_nil? and value == nil}
47+
%{} -> raise Ecto.NoPrimaryKeyValueError, struct: struct
48+
end
49+
end)
50+
51+
if all_nil?, do: {nil, nil}, else: {struct, child_key}
52+
else
53+
{nil, nil}
4454
end
4555

4656
# Traverse sub_structs adding one by one to the tree.

0 commit comments

Comments
 (0)