Packages
ecto
3.2.0
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, QueryExpr, WithExpr}
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})
container_doc("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)
result = container_doc("#Ecto.Query<", list, ">", opts, fn str, _ -> str end)
case query.with_ctes do
%WithExpr{recursive: recursive, queries: [_ | _] = queries} ->
with_ctes =
Enum.map(queries, fn {name, query} ->
cte = __MODULE__.inspect(query, opts)
concat(["|> with_cte(\"" <> name <> "\", as: ", cte, ")"])
end)
recursive_ctes = if recursive, do: "|> recursive_ctes(true)", else: ""
([result, recursive_ctes] ++ with_ctes) |> Enum.intersperse(break("\n")) |> concat()
_ ->
result
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, binding(names, 0))
joins = joins(query.joins, names)
preloads = preloads(query.preloads)
assocs = assocs(query.assocs, names)
windows = windows(query.windows, names)
combinations = combinations(query.combinations)
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,
windows,
combinations,
order_bys,
limit,
offset,
lock,
distinct,
updates,
select,
preloads,
assocs
])
end
defp bound_from(nil, name), do: ["from #{name} in query"]
defp bound_from(%{source: source} = from, name) do
["from #{name} in #{inspect_source(source)}"] ++ kw_as_and_prefix(from)
end
defp inspect_source(%Ecto.Query{} = query), do: "^" <> inspect(query)
defp inspect_source(%Ecto.SubQuery{query: query}), do: "subquery(#{to_string(query)})"
defp inspect_source({source, nil}), do: inspect(source)
defp inspect_source({nil, schema}), do: inspect(schema)
defp inspect_source({source, schema} = from) do
inspect(if source == schema.__schema__(:source), do: schema, else: from)
end
defp joins(joins, names) do
joins
|> Enum.with_index()
|> Enum.flat_map(fn {expr, ix} -> join(expr, binding(names, expr.ix || ix + 1), names) end)
end
defp join(%JoinExpr{qual: qual, assoc: {ix, right}, on: on} = join, name, names) do
string = "#{name} in assoc(#{binding(names, ix)}, #{inspect(right)})"
[{join_qual(qual), string}] ++ kw_as_and_prefix(join) ++ maybe_on(on, names)
end
defp join(
%JoinExpr{qual: qual, source: {:fragment, _, _} = source, on: on} = join = part,
name,
names
) do
string = "#{name} in #{expr(source, names, part)}"
[{join_qual(qual), string}] ++ kw_as_and_prefix(join) ++ [on: expr(on, names)]
end
defp join(%JoinExpr{qual: qual, source: source, on: on} = join, name, names) do
string = "#{name} in #{inspect_source(source)}"
[{join_qual(qual), string}] ++ kw_as_and_prefix(join) ++ [on: expr(on, names)]
end
defp maybe_on(%QueryExpr{expr: true}, _names), do: []
defp maybe_on(%QueryExpr{} = on, names), do: [on: expr(on, names)]
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 windows(windows, names) do
Enum.map(windows, &window(&1, names))
end
defp window({name, %{expr: definition} = part}, names) do
{:windows, "[#{name}: " <> expr(definition, names, part) <> "]"}
end
defp combinations(combinations) do
Enum.map(combinations, fn {key, val} -> {key, "(" <> to_string(val) <> ")"} 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 kw_as_and_prefix(%{as: as, prefix: prefix}) do
kw_inspect(:as, as) ++ kw_inspect(:prefix, prefix)
end
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}(" <> binding(names, ix) <> ", " <> Kernel.inspect(fields) <> ")"
_ ->
binding(names, ix)
end
end
defp expr_to_string({:&, _, [ix]}, _, names, _) do
binding(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]}, _, _, _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(%{from: nil, joins: joins}) do
["query" | join_sources(joins)]
end
defp collect_sources(%{from: %{source: source}, joins: joins}) do
[from_sources(source) | join_sources(joins)]
end
defp from_sources(%Ecto.SubQuery{query: query}), do: from_sources(query.from.source)
defp from_sources({source, schema}), do: schema || source
defp from_sources(nil), do: "query"
defp join_sources(joins) do
joins
|> Enum.sort_by(& &1.ix)
|> Enum.map(fn
%JoinExpr{assoc: {_var, assoc}} ->
assoc
%JoinExpr{source: {:fragment, _, _}} ->
"fragment"
%JoinExpr{source: %Ecto.Query{from: from}} ->
from_sources(from.source)
%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
{names, _} = Enum.map_reduce(letters, 0, &{"#{&1}#{&2}", &2 + 1})
names
end
defp binding(names, pos) do
try do
elem(names, pos)
rescue
ArgumentError -> "unknown_binding_#{pos}!"
end
end
defp normalize_source("Elixir." <> _ = source),
do: source |> Module.split() |> List.last()
defp normalize_source(source),
do: source
end