Packages
ecto
0.7.1
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.Model.t], Ecto.Query.t) :: [Ecto.Model.t]
def query(rows, query)
def query([], _query), do: []
def query(rows, %{assocs: []}), do: rows
def query(rows, %{assocs: assocs, sources: 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
if struct do
child_key = Ecto.Model.primary_key(struct) ||
raise Ecto.MissingPrimaryKeyError, 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 HashSet.member?(keys, cache) do
keys = HashSet.put(keys, cache)
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
HashDict.update(dict, parent_key, [item], &[item|&1])
end
end
{{keys, dict, sub_dicts}, sub_structs}
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 ->
loaded =
dict
|> HashDict.get(child_key, [])
|> Enum.reverse()
|> Enum.map(&load_assocs(&1, sub_dicts, refls))
if refl.cardinality == :one do
loaded = List.first(loaded)
end
Map.put(acc, refl.field, loaded)
end
end
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)
{HashSet.new, HashDict.new, acc}
end
end