Packages
ecto
0.11.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/query/inspect.ex
defimpl Inspect, for: Ecto.Query do
import Inspect.Algebra
alias Ecto.Query.JoinExpr
@doc false
def inspect(query, opts) do
list = Enum.map(to_list(query), fn
{key, string} ->
concat(Atom.to_string(key) <> ": ", string)
string ->
string
end)
surround_many("#Ecto.Query<", list, ">", opts, fn str, _ -> str end)
end
@doc false
def to_string(query) do
Enum.map_join(to_list(query), ",\n ", fn
{key, string} ->
Atom.to_string(key) <> ": " <> string
string ->
string
end)
end
defp to_list(query) do
names =
query
|> collect_sources
|> generate_letters
|> generate_names
|> List.to_tuple
from = bound_from(query.from, elem(names, 0))
joins = joins(query.joins, names)
preloads = preloads(query.preloads)
assocs = assocs(query.assocs, names)
wheres = kw_exprs(:where, query.wheres, names)
group_bys = kw_exprs(:group_by, query.group_bys, names)
havings = kw_exprs(:having, query.havings, names)
order_bys = kw_exprs(:order_by, query.order_bys, names)
lock = kw_inspect(:lock, query.lock)
limit = kw_expr(:limit, query.limit, names)
offset = kw_expr(:offset, query.offset, names)
select = kw_expr(:select, query.select, names)
distinct = kw_expr(:distinct, query.distinct, names)
Enum.concat [from, joins, wheres, group_bys, havings, order_bys,
limit, offset, lock, distinct, select, preloads, assocs]
end
defp bound_from(from, name), do: ["from #{name} in #{unbound_from from}"]
defp unbound_from({source, nil}), do: inspect source
defp unbound_from({nil, model}), do: inspect model
defp unbound_from(from = {source, model}) do
inspecting = if source == model.__schema__(:source), do: model, else: from
inspect inspecting
end
defp unbound_from(nil), do: "query"
defp joins(joins, names) do
joins
|> Enum.with_index
|> Enum.flat_map(fn {expr, ix} -> join(expr, elem(names, expr.ix || ix + 1), names) end)
end
defp join(%JoinExpr{qual: qual, assoc: {ix, right}}, name, names) do
string = "#{name} in assoc(#{elem(names, ix)}, #{inspect right})"
[{join_qual(qual), string}]
end
defp join(%JoinExpr{qual: qual, source: {source, model}, on: on}, name, names) do
string = "#{name} in #{unbound_from {source, model}}"
[{join_qual(qual), string}, on: expr(on, names)]
end
defp preloads([]), do: []
defp preloads(preloads), do: [preload: inspect(preloads)]
defp assocs([], _names), do: []
defp assocs(assocs, names), do: [preload: expr(assocs(assocs), names, %{})]
defp assocs(assocs) do
Enum.map assocs, fn
{field, {idx, []}} ->
{field, {:&, [], [idx]}}
{field, {idx, children}} ->
{field, {{:&, [], [idx]}, assocs(children)}}
end
end
defp kw_exprs(key, exprs, names) do
Enum.map exprs, &{key, expr(&1, names)}
end
defp kw_expr(_key, nil, _names), do: []
defp kw_expr(key, expr, names), do: [{key, expr(expr, names)}]
defp kw_inspect(_key, nil), do: []
defp kw_inspect(key, val), do: [{key, inspect(val)}]
defp expr(%{expr: expr, params: params}, names) do
expr(expr, names, params)
end
defp expr(expr, names, params) do
Macro.to_string(expr, &expr_to_string(&1, &2, names, params))
end
defp expr_to_string({:fragment, _, parts}, _, names, params) do
"fragment(" <> unmerge_fragments(parts, "", [], names, params) <> ")"
end
# Convert variables to proper names
defp expr_to_string({:&, _, [ix]}, _, names, _) do
elem(names, ix)
end
# Inject the interpolated value
#
# In case the query had its parameters removed,
# we use ... to express the interpolated code.
defp expr_to_string({:^, _, [_ix, _len]}, _, _, _params) do
Macro.to_string {:^, [], [{:..., [], nil}]}
end
defp expr_to_string({:^, _, [ix]}, _, _, params) do
escaped =
case Enum.at(params || [], ix) do
{value, _type} -> Macro.escape(value)
_ -> {:..., [], nil}
end
Macro.to_string {:^, [], [escaped]}
end
# Strip trailing ()
defp expr_to_string({{:., _, [_, _]}, _, []}, string, _, _) do
size = byte_size(string)
:binary.part(string, 0, size - 2)
end
# Tagged values
defp expr_to_string(%Ecto.Query.Tagged{value: value, tag: tag}, _, names, params) do
{:type, [], [value, tag]} |> expr(names, params)
end
defp expr_to_string(_expr, string, _, _) do
string
end
defp unmerge_fragments([s, v|t], frag, args, names, params) do
unmerge_fragments(t, frag <> s <> "?", [expr(v, names, params)|args], names, params)
end
defp unmerge_fragments([s], frag, args, _names, _params) do
Enum.join [inspect(frag <> s)|Enum.reverse(args)], ", "
end
defp join_qual(:inner), do: :join
defp join_qual(:left), do: :left_join
defp join_qual(:right), do: :right_join
defp join_qual(:full), do: :full_join
defp collect_sources(query) do
from_sources(query.from) ++ join_sources(query.joins)
end
defp from_sources({source, model}), do: [model || source]
defp from_sources(nil), do: ["query"]
defp join_sources(joins) do
Enum.map(joins, fn
%JoinExpr{assoc: {_var, assoc}} ->
assoc
%JoinExpr{source: {source, model}} ->
model || source
end)
end
defp generate_letters(sources) do
Enum.map(sources, fn source ->
source
|> Kernel.to_string
|> normalize_source
|> String.first
|> String.downcase
end)
end
defp generate_names(letters) do
generate_names(Enum.reverse(letters), [], [])
end
defp generate_names([letter|rest], acc, found) do
index = Enum.count(rest, & &1 == letter)
cond do
index > 0 ->
generate_names(rest, ["#{letter}#{index}"|acc], [letter|found])
letter in found ->
generate_names(rest, ["#{letter}0"|acc], [letter|found])
true ->
generate_names(rest, [letter|acc], found)
end
end
defp generate_names([], acc, _found) do
acc
end
defp normalize_source("Elixir." <> _ = source),
do: source |> Module.split |> List.last
defp normalize_source(source),
do: source
end