Packages
ecto
3.9.3
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 repo modules
# for association related functionality.
@moduledoc false
@doc """
Transforms a result set based on query assocs, loading
the associations onto their parent schema.
"""
@spec query([list], list, tuple, (list -> list)) :: [Ecto.Schema.t]
def query(rows, assocs, sources, fun)
def query([], _assocs, _sources, _fun), do: []
def query(rows, [], _sources, fun), do: Enum.map(rows, fun)
def query(rows, assocs, sources, fun) do
# Create rose tree of accumulator dicts in the same
# structure as the fields tree
accs = create_accs(0, assocs, sources, [])
# Populate tree of dicts of associated entities from the result set
{_keys, _cache, rows, sub_dicts} = Enum.reduce(rows, accs, fn row, acc ->
merge(fun.(row), acc, 0) |> elem(0)
end)
# Create the reflections that will be loaded into memory.
refls = create_refls(0, assocs, sub_dicts, sources)
# Retrieve and load the assocs from cached dictionaries recursively
for {item, sub_structs} <- Enum.reverse(rows) do
[load_assocs(item, refls)|sub_structs]
end
end
defp merge([struct|sub_structs], {primary_keys, cache, dict, sub_dicts}, parent_key) do
child_key =
if struct do
for primary_key <- primary_keys do
case Map.get(struct, primary_key) do
nil -> raise Ecto.NoPrimaryKeyValueError, struct: struct
value -> value
end
end
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))
cache_key = cache_key(parent_key, child_key, sub_structs, dict)
if struct && parent_key && not Map.get(cache, cache_key, false) do
cache = Map.put(cache, cache_key, true)
item = {child_key, struct}
# If we have a list, we are at the root, so we also store the sub structs
dict = update_dict(dict, parent_key, item, sub_structs)
{{primary_keys, cache, dict, sub_dicts}, sub_structs}
else
{{primary_keys, cache, dict, sub_dicts}, sub_structs}
end
end
defp cache_key(parent_key, child_key, sub_structs, dict) when is_list(dict) do
{parent_key, child_key, sub_structs}
end
defp cache_key(parent_key, child_key, _sub_structs, dict) when is_map(dict) do
{parent_key, child_key}
end
defp update_dict(dict, _parent_key, item, sub_structs) when is_list(dict) do
[{item, sub_structs} | dict]
end
defp update_dict(dict, parent_key, item, _sub_structs) when is_map(dict) do
Map.update(dict, parent_key, [item], &[item | &1])
end
defp load_assocs({child_key, struct}, refls) do
Enum.reduce refls, struct, fn {dict, refl, sub_refls}, acc ->
%{field: field, cardinality: cardinality} = refl
loaded =
dict
|> Map.get(child_key, [])
|> Enum.reverse()
|> Enum.map(&load_assocs(&1, sub_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, dicts, sources) do
{_source, schema, _prefix} = elem(sources, idx)
Enum.map(:lists.zip(dicts, fields), fn
{{_primary_keys, _cache, dict, sub_dicts}, {field, {child_idx, child_fields}}} ->
sub_refls = create_refls(child_idx, child_fields, sub_dicts, sources)
{dict, schema.__schema__(:association, field), sub_refls}
end)
end
defp create_accs(idx, fields, sources, initial_dict) do
acc = Enum.map(fields, fn {_field, {child_idx, child_fields}} ->
create_accs(child_idx, child_fields, sources, %{})
end)
{_source, schema, _prefix} = elem(sources, idx)
case schema.__schema__(:primary_key) do
[] -> raise Ecto.NoPrimaryKeyFieldError, schema: schema
pk -> {pk, %{}, initial_dict, acc}
end
end
end