Packages
ecto
2.0.5
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/query/builder/preload.ex
defmodule Ecto.Query.Builder.Preload do
@moduledoc false
alias Ecto.Query.Builder
@doc """
Escapes a preload.
A preload may be an atom, a list of atoms or a keyword list
nested as a rose tree.
iex> escape(:foo, [])
{[:foo], []}
iex> escape([foo: :bar], [])
{[foo: [:bar]], []}
iex> escape([foo: [:bar, bar: :bat]], [])
{[foo: [:bar, bar: [:bat]]], []}
iex> escape([foo: {:^, [], ["external"]}], [])
{[foo: "external"], []}
iex> escape([foo: [:bar, {:^, [], ["external"]}], baz: :bat], [])
{[foo: [:bar, "external"], baz: [:bat]], []}
iex> escape([foo: {:c, [], nil}], [c: 1])
{[], [foo: {1, []}]}
iex> escape([foo: {{:c, [], nil}, bar: {:l, [], nil}}], [c: 1, l: 2])
{[], [foo: {1, [bar: {2, []}]}]}
iex> escape([foo: {:c, [], nil}, bar: {:l, [], nil}], [c: 1, l: 2])
{[], [foo: {1, []}, bar: {2, []}]}
iex> escape([foo: {{:c, [], nil}, :bar}], [c: 1])
{[foo: [:bar]], [foo: {1, []}]}
iex> escape([foo: [bar: {:c, [], nil}]], [c: 1])
** (Ecto.Query.CompileError) cannot preload join association `:bar` with binding `c` because parent preload is not a join association
"""
@spec escape(Macro.t, Keyword.t) :: {[Macro.t], [Macro.t]} | no_return
def escape(preloads, vars) do
{preloads, assocs} = escape(preloads, :both, [], [], vars)
{Enum.reverse(preloads), Enum.reverse(assocs)}
end
defp escape(atom, _mode, preloads, assocs, _vars) when is_atom(atom) do
{[atom|preloads], assocs}
end
defp escape(list, mode, preloads, assocs, vars) when is_list(list) do
Enum.reduce list, {preloads, assocs}, fn item, acc ->
escape_each(item, mode, acc, vars)
end
end
defp escape({:^, _, [inner]}, _mode, preloads, assocs, _vars) do
{[inner|preloads], assocs}
end
defp escape(other, _mode, _preloads, _assocs, _vars) do
Builder.error! "`#{Macro.to_string other}` is not a valid preload expression. " <>
"preload expects an atom, a (nested) list of atoms or a (nested) " <>
"keyword list with a binding, atoms or lists as values. " <>
"Use ^ if you want to interpolate a value"
end
defp escape_each({key, {:^, _, [inner]}}, _mode, {preloads, assocs}, _vars) do
key = escape_key(key)
{[{key, inner}|preloads], assocs}
end
defp escape_each({key, {var, _, context}}, mode, {preloads, assocs}, vars) when is_atom(context) do
assert_assoc!(mode, key, var)
key = escape_key(key)
idx = Builder.find_var!(var, vars)
{preloads, [{key, {idx, []}}|assocs]}
end
defp escape_each({key, {{var, _, context}, list}}, mode, {preloads, assocs}, vars) when is_atom(context) do
assert_assoc!(mode, key, var)
key = escape_key(key)
idx = Builder.find_var!(var, vars)
{inner_preloads, inner_assocs} = escape(list, :assoc, [], [], vars)
assocs = [{key, {idx, Enum.reverse(inner_assocs)}}|assocs]
case inner_preloads do
[] -> {preloads, assocs}
_ -> {[{key, Enum.reverse(inner_preloads)}|preloads], assocs}
end
end
defp escape_each({key, list}, _mode, {preloads, assocs}, vars) do
key = escape_key(key)
{inner_preloads, []} = escape(list, :preload, [], [], vars)
{[{key, Enum.reverse(inner_preloads)}|preloads], assocs}
end
defp escape_each(other, mode, {preloads, assocs}, vars) do
escape(other, mode, preloads, assocs, vars)
end
defp escape_key(atom) when is_atom(atom) do
atom
end
defp escape_key({:^, _, [expr]}) do
quote(do: Ecto.Query.Builder.Preload.key!(unquote(expr)))
end
defp escape_key(other) do
Builder.error! "malformed key in preload `#{Macro.to_string(other)}` in query expression"
end
defp assert_assoc!(mode, _atom, _var) when mode in [:both, :assoc], do: :ok
defp assert_assoc!(_mode, atom, var) do
Builder.error! "cannot preload join association `#{Macro.to_string atom}` with binding `#{var}` " <>
"because parent preload is not a join association"
end
@doc """
Called at runtime to check dynamic preload keys.
"""
def key!(key) when is_atom(key),
do: key
def key!(key) do
raise ArgumentError,
"expected key in preload to be an atom, got: `#{inspect key}`"
end
@doc """
Applies the preloaded value into the query.
The quoted expression should evaluate to a query at runtime.
If possible, it does all calculations at compile time to avoid
runtime work.
"""
@spec build(Macro.t, [Macro.t], Macro.t, Macro.Env.t) :: Macro.t
def build(query, binding, expr, env) do
binding = Builder.escape_binding(binding)
{preloads, assocs} = escape(expr, binding)
Builder.apply_query(query, __MODULE__, [Enum.reverse(preloads), Enum.reverse(assocs)], env)
end
@doc """
The callback applied by `build/4` to build the query.
"""
@spec apply(Ecto.Queryable.t, term, term) :: Ecto.Query.t
def apply(query, preloads, assocs) do
query = Ecto.Queryable.to_query(query)
%{query | preloads: query.preloads ++ preloads, assocs: query.assocs ++ assocs}
end
end