zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

assoc.ex (4069B)


      1 defmodule Ecto.Repo.Assoc do
      2   # The module invoked by repo modules
      3   # for association related functionality.
      4   @moduledoc false
      5 
      6   @doc """
      7   Transforms a result set based on query assocs, loading
      8   the associations onto their parent schema.
      9   """
     10   @spec query([list], list, tuple, (list -> list)) :: [Ecto.Schema.t]
     11   def query(rows, assocs, sources, fun)
     12 
     13   def query([], _assocs, _sources, _fun), do: []
     14   def query(rows, [], _sources, fun), do: Enum.map(rows, fun)
     15 
     16   def query(rows, assocs, sources, fun) do
     17     # Create rose tree of accumulator dicts in the same
     18     # structure as the fields tree
     19     accs = create_accs(0, assocs, sources, [])
     20 
     21     # Populate tree of dicts of associated entities from the result set
     22     {_keys, _cache, rows, sub_dicts} = Enum.reduce(rows, accs, fn row, acc ->
     23       merge(fun.(row), acc, 0) |> elem(0)
     24     end)
     25 
     26     # Create the reflections that will be loaded into memory.
     27     refls = create_refls(0, assocs, sub_dicts, sources)
     28 
     29     # Retrieve and load the assocs from cached dictionaries recursively
     30     for {item, sub_structs} <- Enum.reverse(rows) do
     31       [load_assocs(item, refls)|sub_structs]
     32     end
     33   end
     34 
     35   defp merge([struct|sub_structs], {primary_keys, cache, dict, sub_dicts}, parent_key) do
     36     child_key =
     37       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
     44       end
     45 
     46     # Traverse sub_structs adding one by one to the tree.
     47     # Note we need to traverse even if we don't have a child_key
     48     # due to nested associations.
     49     {sub_dicts, sub_structs} = Enum.map_reduce(sub_dicts, sub_structs, &merge(&2, &1, child_key))
     50 
     51     cache_key = cache_key(parent_key, child_key, sub_structs, dict)
     52 
     53     if struct && parent_key && not Map.get(cache, cache_key, false) do
     54       cache = Map.put(cache, cache_key, true)
     55       item = {child_key, struct}
     56 
     57       # If we have a list, we are at the root, so we also store the sub structs
     58       dict = update_dict(dict, parent_key, item, sub_structs)
     59 
     60       {{primary_keys, cache, dict, sub_dicts}, sub_structs}
     61     else
     62       {{primary_keys, cache, dict, sub_dicts}, sub_structs}
     63     end
     64   end
     65 
     66   defp cache_key(parent_key, child_key, sub_structs, dict) when is_list(dict) do
     67     {parent_key, child_key, sub_structs}
     68   end
     69 
     70   defp cache_key(parent_key, child_key, _sub_structs, dict) when is_map(dict) do
     71     {parent_key, child_key}
     72   end
     73 
     74   defp update_dict(dict, _parent_key, item, sub_structs) when is_list(dict) do
     75     [{item, sub_structs} | dict]
     76   end
     77 
     78   defp update_dict(dict, parent_key, item, _sub_structs) when is_map(dict) do
     79     Map.update(dict, parent_key, [item], &[item | &1])
     80   end
     81 
     82   defp load_assocs({child_key, struct}, refls) do
     83     Enum.reduce refls, struct, fn {dict, refl, sub_refls}, acc ->
     84       %{field: field, cardinality: cardinality} = refl
     85       loaded =
     86         dict
     87         |> Map.get(child_key, [])
     88         |> Enum.reverse()
     89         |> Enum.map(&load_assocs(&1, sub_refls))
     90         |> maybe_first(cardinality)
     91       Map.put(acc, field, loaded)
     92     end
     93   end
     94 
     95   defp maybe_first(list, :one), do: List.first(list)
     96   defp maybe_first(list, _), do: list
     97 
     98   defp create_refls(idx, fields, dicts, sources) do
     99     {_source, schema, _prefix} = elem(sources, idx)
    100 
    101     Enum.map(:lists.zip(dicts, fields), fn
    102       {{_primary_keys, _cache, dict, sub_dicts}, {field, {child_idx, child_fields}}} ->
    103         sub_refls = create_refls(child_idx, child_fields, sub_dicts, sources)
    104         {dict, schema.__schema__(:association, field), sub_refls}
    105     end)
    106   end
    107 
    108   defp create_accs(idx, fields, sources, initial_dict) do
    109     acc = Enum.map(fields, fn {_field, {child_idx, child_fields}} ->
    110       create_accs(child_idx, child_fields, sources, %{})
    111     end)
    112 
    113     {_source, schema, _prefix} = elem(sources, idx)
    114 
    115     case schema.__schema__(:primary_key) do
    116       [] -> raise Ecto.NoPrimaryKeyFieldError, schema: schema
    117       pk -> {pk, %{}, initial_dict, acc}
    118     end
    119   end
    120 end