Packages
ecto
2.2.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
import Inspect.Algebra
import Kernel, except: [to_string: 1]
alias Ecto.Query.{DynamicExpr, JoinExpr}
defimpl Inspect, for: Ecto.Query.DynamicExpr do
def inspect(%DynamicExpr{binding: binding} = dynamic, opts) do
{expr, binding, params, _, _} =
Ecto.Query.Builder.Dynamic.fully_expand(%Ecto.Query{joins: Enum.drop(binding, 1)}, dynamic)
names =
for {name, _, _} <- binding, do: Atom.to_string(name)
inspected =
Inspect.Ecto.Query.expr(expr, List.to_tuple(names), %{expr: expr, params: params})
surround_many("dynamic(", [Macro.to_string(binding), inspected], ")", opts, fn str, _ -> str end)
end
end
defimpl Inspect, for: Ecto.Query do
@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 = bool_exprs(%{and: :where, or: :or_where}, query.wheres, names)
group_bys = kw_exprs(:group_by, query.group_bys, names)
havings = bool_exprs(%{and: :having, or: :or_having}, query.havings, names)
order_bys = kw_exprs(:order_by, query.order_bys, names)
updates = kw_exprs(:update, query.updates, 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, updates, select, preloads, assocs]
end
defp bound_from(from, name), do: ["from #{name} in #{unbound_from from}"]
defp unbound_from(nil), do: "query"
defp unbound_from({source, nil}), do: inspect source
defp unbound_from({nil, schema}), do: inspect schema
defp unbound_from(from = {source, schema}) do
inspect if source == schema.__schema__(:source), do: schema, else: from
end
defp unbound_from(%Ecto.SubQuery{query: query}) do
"subquery(#{to_string query})"
end
defp unbound_from(%Ecto.Query{} = query) do
"^" <> inspect(query)
end
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: {:fragment, _, _} = source, on: on} = part, name, names) do
string = "#{name} in #{expr(source, names, part)}"
[{join_qual(qual), string}, on: expr(on, names)]
end
defp join(%JoinExpr{qual: qual, source: source, on: on}, name, names) do
string = "#{name} in #{unbound_from source}"
[{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 bool_exprs(keys, exprs, names) do
Enum.map exprs, fn %{expr: expr, op: op} = part ->
{Map.fetch!(keys, op), expr(expr, names, part)}
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} = part, names) do
expr(expr, names, part)
end
@doc false
def expr(expr, names, part) do
Macro.to_string(expr, &expr_to_string(&1, &2, names, part))
end
# For keyword and interpolated fragments use normal escaping
defp expr_to_string({:fragment, _, [{_, _}|_] = parts}, _, names, part) do
"fragment(" <> unmerge_fragments(parts, "", [], names, part) <> ")"
end
# Convert variables to proper names
defp expr_to_string({:&, _, [ix]}, _, names, %{take: take}) do
case take do
%{^ix => {:any, fields}} when ix == 0 ->
Kernel.inspect(fields)
%{^ix => {tag, fields}} ->
"#{tag}(" <> elem(names, ix) <> ", " <> Kernel.inspect(fields) <> ")"
_ ->
elem(names, ix)
end
end
defp expr_to_string({:&, _, [ix]}, _, names, _) do
try do
elem(names, ix)
rescue
ArgumentError -> "unknown_binding!"
end
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]}, _, _, _part) do
Macro.to_string {:^, [], [{:..., [], nil}]}
end
defp expr_to_string({:^, _, [ix]}, _, _, %{params: params}) do
case Enum.at(params || [], ix) do
{value, _type} -> "^" <> Kernel.inspect(value, charlists: :as_lists)
_ -> "^..."
end
end
# Strip trailing ()
defp expr_to_string({{:., _, [_, _]}, _, []}, string, _, _) do
size = byte_size(string)
:binary.part(string, 0, size - 2)
end
# Types need to be converted back to AST for fields
defp expr_to_string({:type, [], [expr, type]}, _string, names, part) do
"type(#{expr(expr, names, part)}, #{type |> type_to_expr() |> expr(names, part)})"
end
# Tagged values
defp expr_to_string(%Ecto.Query.Tagged{value: value, tag: nil}, _, _names, _) do
inspect value
end
defp expr_to_string(%Ecto.Query.Tagged{value: value, tag: tag}, _, names, part) do
{:type, [], [value, tag]} |> expr(names, part)
end
defp expr_to_string(_expr, string, _, _) do
string
end
defp type_to_expr({composite, type}) when is_atom(composite) do
{composite, type_to_expr(type)}
end
defp type_to_expr({part, type}) when is_integer(part) do
{{:., [], [{:&, [], [part]}, type]}, [], []}
end
defp type_to_expr(type) do
type
end
defp unmerge_fragments([{:raw, s}, {:expr, v}|t], frag, args, names, part) do
unmerge_fragments(t, frag <> s <> "?", [expr(v, names, part)|args], names, part)
end
defp unmerge_fragments([{:raw, s}], frag, args, _names, _part) do
Enum.join [inspect(frag <> s)|Enum.reverse(args)], ", "
end
defp join_qual(:inner), do: :join
defp join_qual(:inner_lateral), do: :join_lateral
defp join_qual(:left), do: :left_join
defp join_qual(:left_lateral), do: :left_join_lateral
defp join_qual(:right), do: :right_join
defp join_qual(:full), do: :full_join
defp join_qual(:cross), do: :cross_join
defp collect_sources(query) do
[from_sources(query.from) | join_sources(query.joins)]
end
defp from_sources(%Ecto.SubQuery{query: query}), do: from_sources(query.from)
defp from_sources({source, schema}), do: schema || source
defp from_sources(nil), do: "query"
defp join_sources(joins) do
Enum.map(joins, fn
%JoinExpr{assoc: {_var, assoc}} ->
assoc
%JoinExpr{source: {:fragment, _, _}} ->
"fragment"
%JoinExpr{source: %Ecto.Query{from: from}} ->
from_sources(from)
%JoinExpr{source: source} ->
from_sources(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