Packages
ecto
2.0.0-rc.2
3.14.1
3.14.0
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.2
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.4
3.8.3
3.8.2
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.1
3.0.0-rc.0
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.2.0-rc.1
2.2.0-rc.0
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.1.0-rc.5
2.1.0-rc.4
2.1.0-rc.3
2.1.0-rc.2
2.1.0-rc.1
2.1.0-rc.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-beta.2
2.0.0-beta.1
2.0.0-beta.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.12.0-rc
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
A toolkit for data mapping and language integrated query for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/repo/assoc.ex
defmodule Ecto.Repo.Assoc do
# The module invoked by repomodules
# for association related functionality.
@moduledoc false
@doc """
Transforms a result set based on query assocs, loading
the associations onto their parent model.
"""
@spec query([Ecto.Schema.t], list, tuple) :: [Ecto.Schema.t]
def query(rows, assocs, sources)
def query([], _assocs, _sources), do: []
def query(rows, [], _sources), do: rows
def query(rows, assocs, sources) do
# Pre-create rose tree of reflections and accumulator
# dicts in the same structure as the fields tree
refls = create_refls(0, assocs, sources)
accs = create_accs(assocs)
# Replace the dict in the accumulator by a list
# We use it as a flag to store the substructs
accs = put_elem(accs, 1, [])
# Populate tree of dicts of associated entities from the result set
{_keys, rows, sub_dicts} = Enum.reduce(rows, accs, fn row, acc ->
merge(row, acc, 0) |> elem(0)
end)
# Retrieve and load the assocs from cached dictionaries recursively
for {item, sub_structs} <- Enum.reverse(rows) do
[load_assocs(item, sub_dicts, refls)|sub_structs]
end
end
defp merge([struct|sub_structs], {keys, dict, sub_dicts}, parent_key) do
child_key =
if struct do
[{_, key}] = Ecto.primary_key!(struct)
key || raise Ecto.NoPrimaryKeyValueError, struct: struct
end
# Traverse sub_structs adding one by one to the tree.
# Note we need to traverse even if we don't have a child_key
# due to nested associations.
{sub_dicts, sub_structs} =
Enum.map_reduce sub_dicts, sub_structs, &merge(&2, &1, child_key)
# Now if we have a struct and its parent key, we store the current
# data unless we have already processed it.
cache = {parent_key, child_key}
if struct && parent_key && not Map.get(keys, cache, false) do
keys = Map.put(keys, cache, true)
item = {child_key, struct}
# If we have a list, we are at the root,
# so we also store the sub structs
dict =
if is_list(dict) do
[{item, sub_structs}|dict]
else
Map.update(dict, parent_key, [item], &[item|&1])
end
{{keys, dict, sub_dicts}, sub_structs}
else
{{keys, dict, sub_dicts}, sub_structs}
end
end
defp load_assocs({child_key, struct}, sub_dicts, refls) do
Enum.reduce :lists.zip(sub_dicts, refls), struct, fn
{{_keys, dict, sub_dicts}, {refl, refls}}, acc ->
%{field: field, cardinality: cardinality} = refl
loaded =
dict
|> Map.get(child_key, [])
|> Enum.reverse()
|> Enum.map(&load_assocs(&1, sub_dicts, refls))
|> maybe_first(cardinality)
Map.put(acc, field, loaded)
end
end
defp maybe_first(list, :one), do: List.first(list)
defp maybe_first(list, _), do: list
defp create_refls(idx, fields, sources) do
Enum.map(fields, fn {field, {child_idx, child_fields}} ->
{_source, model} = elem(sources, idx)
{model.__schema__(:association, field),
create_refls(child_idx, child_fields, sources)}
end)
end
defp create_accs(fields) do
acc = Enum.map(fields, fn {_field, {_child_idx, child_fields}} ->
create_accs(child_fields)
end)
{%{}, %{}, acc}
end
end