Packages
ecto
1.1.2
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/planner.ex
defmodule Ecto.Query.Planner do
# Normalizes a query and its parameters.
@moduledoc false
alias Ecto.Query.SelectExpr
alias Ecto.Query.JoinExpr
if map_size(%Ecto.Query{}) != 17 do
raise "Ecto.Query match out of date in builder"
end
@doc """
Validates and cast the given fields belonging to the given model.
"""
def fields(model, kind, kw, adapter) do
# It is ok to use __changeset__ because
# we have already filtered the results
# to be only about fields.
types = model.__changeset__
for {field, value} <- kw do
type = Map.get(types, field)
unless type do
raise Ecto.ChangeError,
message: "field `#{inspect model}.#{field}` in `#{kind}` does not exist in the model source"
end
case adapter.dump(type, value) do
{:ok, value} ->
{field, value}
:error ->
raise Ecto.ChangeError,
message: "value `#{inspect value}` for `#{inspect model}.#{field}` " <>
"in `#{kind}` does not match type #{inspect type}"
end
end
end
@doc """
Plans the query for execution.
Planning happens in multiple steps:
1. First the query is prepared by retrieving
its cache key, casting and merging parameters
2. Then a cache lookup is done, if the query is
cached, we are done
3. If there is no cache, we need to actually
normalize and validate the query, asking the
adapter to prepare it
4. The query is sent to the adapter to be generated
## Cache
All entries in the query, except the preload and sources
field, should be part of the cache key.
The cache value is the compiled query by the adapter
along-side the select expression.
"""
def query(query, operation, repo, adapter) do
{query, params, key} = prepare(query, operation, adapter)
if key == :nocache do
{_, select, prepared} = query_without_cache(query, operation, adapter)
{build_meta(query, select), prepared, params}
else
table = repo.__query_cache__
case cache_lookup(repo, table, key) do
[{_, select, prepared}] ->
{build_meta(query, select), prepared, params}
[] ->
case query_without_cache(query, operation, adapter) do
{:cache, select, prepared} ->
:ets.insert(table, {key, select, prepared})
{build_meta(query, select), prepared, params}
{:nocache, select, prepared} ->
{build_meta(query, select), prepared, params}
end
end
end
end
defp cache_lookup(repo, table, key) do
:ets.lookup(table, key)
rescue
ArgumentError ->
raise ArgumentError,
"repo #{inspect repo} is not started, please ensure it is part of your supervision tree"
end
defp query_without_cache(query, operation, adapter) do
%{select: select} = query = normalize(query, operation, adapter)
{cache, prepared} = adapter.prepare(operation, query)
{cache, select && %{select | file: nil, line: nil}, prepared}
end
defp build_meta(%{prefix: prefix, sources: sources,
assocs: assocs, preloads: preloads}, select) do
%{prefix: prefix, sources: sources,
assocs: assocs, preloads: preloads, select: select}
end
@doc """
Prepares the query for cache.
This means all the parameters from query expressions are
merged into a single value and their entries are prunned
from the query.
This function is called by the backend before invoking
any cache mechanism.
"""
def prepare(query, operation, adapter) do
query
|> prepare_sources
|> prepare_cache(operation, adapter)
rescue
e ->
# Reraise errors so we ignore the planner inner stacktrace
reraise e
end
@doc """
Prepare the parameters by merging and casting them according to sources.
"""
def prepare_cache(query, operation, adapter) do
{query, {cache, params}} =
traverse_exprs(query, operation, {[], []}, &{&3, merge_cache(&1, &2, &3, &4, adapter)})
{query, Enum.reverse(params), finalize_cache(query, operation, cache)}
end
defp merge_cache(kind, query, expr, {cache, params}, adapter)
when kind in ~w(select distinct limit offset)a do
if expr do
{params, cacheable?} = cast_and_merge_params(kind, query, expr, params, adapter)
{merge_cache({kind, expr.expr}, cache, cacheable?), params}
else
{cache, params}
end
end
defp merge_cache(kind, query, exprs, {cache, params}, adapter)
when kind in ~w(where update group_by having order_by)a do
{expr_cache, {params, cacheable?}} =
Enum.map_reduce exprs, {params, true}, fn expr, {params, cacheable?} ->
{params, current_cacheable?} = cast_and_merge_params(kind, query, expr, params, adapter)
{expr.expr, {params, cacheable? and current_cacheable?}}
end
case expr_cache do
[] -> {cache, params}
_ -> {merge_cache({kind, expr_cache}, cache, cacheable?), params}
end
end
defp merge_cache(:join, query, exprs, {cache, params}, adapter) do
{expr_cache, {params, cacheable?}} =
Enum.map_reduce exprs, {params, true}, fn
%JoinExpr{on: on, qual: qual, source: source} = join, {params, cacheable?} ->
{params, join_cacheable?} = cast_and_merge_params(:join, query, join, params, adapter)
{params, on_cacheable?} = cast_and_merge_params(:join, query, on, params, adapter)
{{qual, source_cache(source), on.expr},
{params, cacheable? and join_cacheable? and on_cacheable?}}
end
case expr_cache do
[] -> {cache, params}
_ -> {merge_cache({:join, expr_cache}, cache, cacheable?), params}
end
end
defp cast_and_merge_params(kind, query, expr, params, adapter) do
Enum.reduce expr.params, {params, true}, fn
{v, {:in_spread, type}}, {acc, _cacheable?} ->
{unfold_in(cast_param(kind, query, expr, v, {:array, type}, adapter), acc), false}
{v, type}, {acc, cacheable?} ->
{[cast_param(kind, query, expr, v, type, adapter)|acc], cacheable?}
end
end
defp merge_cache(_left, _right, false), do: :nocache
defp merge_cache(_left, :nocache, true), do: :nocache
defp merge_cache(left, right, true), do: [left|right]
defp finalize_cache(_query, _operation, :nocache) do
:nocache
end
defp finalize_cache(query, operation, cache) do
if (assocs = query.assocs) && assocs != [] do
cache = [assocs: assocs] ++ cache
end
if prefix = query.prefix do
cache = [prefix: prefix] ++ cache
end
if lock = query.lock do
cache = [lock: lock] ++ cache
end
[operation, source_cache(query.from)|cache]
end
defp source_cache({_, nil} = source), do: source
defp source_cache({bin, model}), do: {bin, model, model.__schema__(:hash)}
defp source_cache({:fragment, _, _} = source), do: source
defp cast_param(kind, query, expr, v, type, adapter) do
{model, field, type} = type_for_param!(kind, query, expr, type)
cast = cast_param(kind, type, v)
try do
case cast do
{:dump, type, v} ->
case adapter.dump(type, v) do
{:ok, v} -> v
:error -> error! query, expr, "cannot dump cast value `#{inspect v}` to type #{inspect type}"
end
{:error, error} ->
error! query, expr, error
end
catch
:error, %Ecto.QueryError{} = e when not is_nil(model) ->
raise Ecto.CastError, model: model, field: field, value: v, type: type,
message: Exception.message(e) <>
"\nError when casting value to `#{inspect model}.#{field}`"
end
end
defp cast_param(kind, {:in_array, {:array, type}}, value) do
cast_param(kind, type, value)
end
defp cast_param(kind, {:in_array, :any}, value) do
cast_param(kind, :any, value)
end
defp cast_param(kind, {:in_array, other}, value) do
{:error, "value `#{inspect value}` in `#{kind}` expected to be part of an array " <>
"but matched type is #{inspect other}"}
end
defp cast_param(kind, type, nil) when kind != :update do
{:error, "value `nil` in `#{kind}` cannot be cast to type #{inspect type} " <>
" (if you want to check for nils, use is_nil/1 instead)"}
end
defp cast_param(kind, type, v) do
case Ecto.Type.cast(type, v) do
{:ok, v} ->
{:dump, type, v}
:error ->
{:error, "value `#{inspect v}` in `#{kind}` cannot be cast to type #{inspect type}"}
end
end
defp unfold_in(%Ecto.Query.Tagged{value: value, type: {:array, type}}, acc),
do: unfold_in(value, type, acc)
defp unfold_in(value, acc) when is_list(value),
do: Enum.reverse(value, acc)
defp unfold_in([h|t], type, acc),
do: unfold_in(t, type, [%Ecto.Query.Tagged{value: h, type: type}|acc])
defp unfold_in([], _type, acc),
do: acc
@doc """
Prepare all sources, by traversing and expanding joins.
"""
def prepare_sources(query) do
from = query.from || error!(query, "query must have a from expression")
{joins, sources, tail_sources} = prepare_joins(query, [from], length(query.joins))
%{query | sources: (tail_sources ++ sources) |> Enum.reverse |> List.to_tuple(),
joins: joins |> Enum.reverse}
end
defp prepare_joins(query, sources, offset) do
prepare_joins(query.joins, query, [], sources, [], 1, offset)
end
defp prepare_joins([%JoinExpr{assoc: {ix, assoc}, qual: qual} = join|t],
query, joins, sources, tail_sources, counter, offset) do
{source, model} = Enum.fetch!(Enum.reverse(sources), ix)
unless model do
error! query, join, "cannot perform association join on #{inspect source} " <>
"because it does not have a model"
end
refl = model.__schema__(:association, assoc)
unless refl do
error! query, join, "could not find association `#{assoc}` on model #{inspect model}"
end
# If we have the following join:
#
# from p in Post,
# join: p in assoc(p, :comments)
#
# The callback below will return a query that contains only
# joins in a way it starts with the Post and ends in the
# Comment.
#
# This means we need to rewrite the joins below to properly
# shift the &... identifier in a way that:
#
# &0 -> becomes assoc ix
# &LAST_JOIN -> becomes counter
#
# All values in the middle should be shifted by offset,
# all values after join are already correct.
child = refl.__struct__.joins_query(refl)
last_ix = length(child.joins)
source_ix = counter
{child_joins, child_sources, child_tail} =
prepare_joins(child, [child.from], offset + last_ix - 1)
# Rewrite joins indexes as mentioned above
child_joins = Enum.map(child_joins, &rewrite_join(&1, qual, ix, last_ix, source_ix, offset))
# Drop the last resource which is the association owner (it is reversed)
child_sources = Enum.drop(child_sources, -1)
[current_source|child_sources] = child_sources
child_sources = child_tail ++ child_sources
prepare_joins(t, query, child_joins ++ joins, [current_source|sources],
child_sources ++ tail_sources, counter + 1, offset + length(child_sources))
end
defp prepare_joins([%JoinExpr{source: {source, model}} = join|t],
query, joins, sources, tail_sources, counter, offset) when is_atom(model) and model != nil do
source = if is_binary(source), do: {source, model}, else: {model.__schema__(:source), model}
join = %{join | source: source, ix: counter}
prepare_joins(t, query, [join|joins], [source|sources], tail_sources, counter + 1, offset)
end
defp prepare_joins([%JoinExpr{source: source} = join|t],
query, joins, sources, tail_sources, counter, offset) do
join = %{join | source: source, ix: counter}
prepare_joins(t, query, [join|joins], [source|sources], tail_sources, counter + 1, offset)
end
defp prepare_joins([], _query, joins, sources, tail_sources, _counter, _offset) do
{joins, sources, tail_sources}
end
defp rewrite_join(%{on: on, ix: join_ix} = join, qual, ix, last_ix, source_ix, inc_ix) do
on = update_in on.expr, fn expr ->
Macro.prewalk expr, fn
{:&, meta, [join_ix]} ->
{:&, meta, [rewrite_ix(join_ix, ix, last_ix, source_ix, inc_ix)]}
other ->
other
end
end
%{join | on: on, qual: qual,
ix: rewrite_ix(join_ix, ix, last_ix, source_ix, inc_ix)}
end
# We need to replace the source by the one from the assoc
defp rewrite_ix(0, ix, _last_ix, _source_ix, _inc_x), do: ix
# The last entry will have the current source index
defp rewrite_ix(last_ix, _ix, last_ix, source_ix, _inc_x), do: source_ix
# All above last are already correct
defp rewrite_ix(join_ix, _ix, last_ix, _source_ix, _inc_ix) when join_ix > last_ix, do: join_ix
# All others need to be incremented by the offset sources
defp rewrite_ix(join_ix, _ix, _last_ix, _source_ix, inc_ix), do: join_ix + inc_ix
@doc """
Normalizes the query.
After the query was prepared and there is no cache
entry, we need to update its interpolations and check
its fields and associations exist and are valid.
"""
def normalize(query, operation, adapter) do
case operation do
:all ->
assert_no_update!(query, operation)
:update_all ->
assert_update!(query, operation)
assert_only_filter_expressions!(query, operation)
:delete_all ->
assert_no_update!(query, operation)
assert_only_filter_expressions!(query, operation)
end
query
|> traverse_exprs(operation, 0, &validate_and_increment(&1, &2, &3, &4, adapter))
|> elem(0)
|> normalize_select(operation)
|> validate_assocs
rescue
e ->
# Reraise errors so we ignore the planner inner stacktrace
reraise e
end
defp validate_and_increment(kind, query, expr, counter, adapter)
when kind in ~w(select distinct limit offset)a do
if expr do
validate_and_increment_each(kind, query, expr, counter, adapter)
else
{nil, counter}
end
end
defp validate_and_increment(kind, query, exprs, counter, adapter)
when kind in ~w(where group_by having order_by update)a do
{exprs, counter} =
Enum.reduce(exprs, {[], counter}, fn
%{expr: []}, {list, acc} ->
{list, acc}
expr, {list, acc} ->
{expr, acc} = validate_and_increment_each(kind, query, expr, acc, adapter)
{[expr|list], acc}
end)
{Enum.reverse(exprs), counter}
end
defp validate_and_increment(:join, query, exprs, counter, adapter) do
Enum.map_reduce exprs, counter, fn join, acc ->
{source, acc} = validate_and_increment_each(:join, query, join, join.source, acc, adapter)
{on, acc} = validate_and_increment_each(:join, query, join.on, acc, adapter)
{%{join | on: on, source: source, params: nil}, acc}
end
end
defp validate_and_increment_each(kind, query, expr, counter, adapter) do
{inner, acc} = validate_and_increment_each(kind, query, expr, expr.expr, counter, adapter)
{%{expr | expr: inner, params: nil}, acc}
end
defp validate_and_increment_each(kind, query, expr, ast, counter, adapter) do
Macro.prewalk ast, counter, fn
{:in, in_meta, [left, {:^, meta, [param]}]}, acc ->
{right, acc} = validate_in(meta, expr, param, acc)
{{:in, in_meta, [left, right]}, acc}
{:^, meta, [ix]}, acc when is_integer(ix) ->
{{:^, meta, [acc]}, acc + 1}
{{:., _, [{:&, _, [source]}, field]} = dot, meta, []}, acc ->
type = type!(kind, query, expr, source, field)
{{dot, [ecto_type: type] ++ meta, []}, acc}
{:type, _, [{:^, meta, [ix]}, _expr]}, acc when is_integer(ix) ->
{_, t} = Enum.fetch!(expr.params, ix)
{_, _, type} = type_for_param!(kind, query, expr, t)
{%Ecto.Query.Tagged{value: {:^, meta, [acc]}, tag: type,
type: Ecto.Type.type(type)}, acc + 1}
%Ecto.Query.Tagged{value: v, type: type}, acc ->
{cast_param(kind, query, expr, v, type, adapter), acc}
other, acc ->
{other, acc}
end
end
defp validate_in(meta, expr, param, acc) do
{v, _t} = Enum.fetch!(expr.params, param)
length = length(v)
case length do
0 -> {[], acc}
_ -> {{:^, meta, [acc, length]}, acc + length}
end
end
defp normalize_select(query, operation) do
cond do
operation in [:update_all, :delete_all] ->
query
select = query.select ->
%{query | select: normalize_fields(query, select)}
true ->
select = %SelectExpr{expr: {:&, [], [0]}}
%{query | select: normalize_fields(query, select)}
end
end
defp normalize_fields(%{assocs: [], preloads: []} = query, select) do
{fields, from?} = collect_fields(query, select.expr, false)
fields =
if from? do
[{:&, [], [0]}|fields]
else
fields
end
%{select | fields: fields}
end
defp normalize_fields(%{assocs: assocs} = query, select) do
{fields, from?} = collect_fields(query, select.expr, false)
unless from? do
error! query, "the binding used in `from` must be selected in `select` when using `preload`"
end
assocs = collect_assocs(assocs)
fields = [{:&, [], [0]}|assocs] ++ fields
%{select | fields: fields}
end
defp collect_fields(_query, {:&, _, [ix]} = expr, from?) do
if ix == 0 do
{[], true}
else
{[expr], from?}
end
end
defp collect_fields(query, {left, right}, from?),
do: collect_fields(query, [left, right], from?)
defp collect_fields(query, {:{}, _, elems}, from?),
do: collect_fields(query, elems, from?)
defp collect_fields(query, {:%{}, _, pairs}, from?),
do: collect_fields(query, Enum.map(pairs, &elem(&1, 1)), from?)
defp collect_fields(query, list, from?) when is_list(list),
do: Enum.flat_map_reduce(list, from?, &collect_fields(query, &1, &2))
defp collect_fields(_query, expr, from?),
do: {[expr], from?}
defp collect_assocs([{_assoc, {ix, children}}|tail]),
do: [{:&, [], [ix]}] ++ collect_assocs(children) ++ collect_assocs(tail)
defp collect_assocs([]),
do: []
defp validate_assocs(query) do
validate_assocs(query, 0, query.assocs)
query
end
defp validate_assocs(query, ix, assocs) do
# We validate the model exists when normalizing fields above
{_, parent_model} = elem(query.sources, ix)
Enum.each assocs, fn {assoc, {child_ix, child_assocs}} ->
refl = parent_model.__schema__(:association, assoc)
unless refl do
error! query, "field `#{inspect parent_model}.#{assoc}` " <>
"in preload is not an association"
end
case find_source_expr(query, child_ix) do
%JoinExpr{qual: qual} when qual in [:inner, :left] ->
:ok
%JoinExpr{qual: qual} ->
error! query, "association `#{inspect parent_model}.#{assoc}` " <>
"in preload requires an inner or left join, got #{qual} join"
_ ->
:ok
end
validate_assocs(query, child_ix, child_assocs)
end
end
defp find_source_expr(query, 0) do
query.from
end
defp find_source_expr(query, ix) do
Enum.find(query.joins, & &1.ix == ix)
end
## Helpers
# Traverse all query components with expressions.
# Therefore from, preload, assocs and lock are not traversed.
defp traverse_exprs(original, operation, acc, fun) do
query = original
if operation == :update_all do
{updates, acc} = fun.(:update, original, original.updates, acc)
query = %{query | updates: updates}
end
{select, acc} = fun.(:select, original, original.select, acc)
query = %{query | select: select}
{distinct, acc} = fun.(:distinct, original, original.distinct, acc)
query = %{query | distinct: distinct}
{joins, acc} = fun.(:join, original, original.joins, acc)
query = %{query | joins: joins}
{wheres, acc} = fun.(:where, original, original.wheres, acc)
query = %{query | wheres: wheres}
{group_bys, acc} = fun.(:group_by, original, original.group_bys, acc)
query = %{query | group_bys: group_bys}
{havings, acc} = fun.(:having, original, original.havings, acc)
query = %{query | havings: havings}
{order_bys, acc} = fun.(:order_by, original, original.order_bys, acc)
query = %{query | order_bys: order_bys}
{limit, acc} = fun.(:limit, original, original.limit, acc)
query = %{query | limit: limit}
{offset, acc} = fun.(:offset, original, original.offset, acc)
{%{query | offset: offset}, acc}
end
defp type!(_kind, _query, _expr, nil, _field), do: :any
defp type!(kind, query, expr, source, field) when is_integer(source) do
case elem(query.sources, source) do
{_, model} -> type!(kind, query, expr, model, field)
{:fragment, _, _} -> :any
end
end
defp type!(kind, query, expr, model, field) when is_atom(model) do
if type = model.__schema__(:type, field) do
type
else
error! query, expr, "field `#{inspect model}.#{field}` in `#{kind}` " <>
"does not exist in the model source"
end
end
defp type_for_param!(kind, query, expr, {composite, {ix, field}}) when is_integer(ix) do
{_, model} = elem(query.sources, ix)
{model, field, {composite, type!(kind, query, expr, model, field)}}
end
defp type_for_param!(kind, query, expr, {ix, field}) when is_integer(ix) do
{_, model} = elem(query.sources, ix)
{model, field, type!(kind, query, expr, model, field)}
end
defp type_for_param!(_kind, _query, _expr, type) do
{nil, nil, type}
end
defp assert_update!(%Ecto.Query{updates: updates} = query, operation) do
changes =
Enum.reduce(updates, %{}, fn update, acc ->
Enum.reduce(update.expr, acc, fn {_op, kw}, acc ->
Enum.reduce(kw, acc, fn {k, v}, acc ->
Map.update(acc, k, v, fn _ ->
error! query, "duplicate field `#{k}` for `#{operation}`"
end)
end)
end)
end)
if changes == %{} do
error! query, "`#{operation}` requires at least one field to be updated"
end
end
defp assert_no_update!(query, operation) do
case query do
%Ecto.Query{updates: []} -> query
_ ->
error! query, "`#{operation}` does not allow `update` expressions"
end
end
defp assert_only_filter_expressions!(query, operation) do
case query do
%Ecto.Query{select: nil, order_bys: [], limit: nil, offset: nil,
group_bys: [], havings: [], preloads: [], assocs: [],
distinct: nil, lock: nil} ->
query
_ ->
error! query, "`#{operation}` allows only `where` and `join` expressions"
end
end
defp reraise(exception) do
reraise exception, Enum.reject(System.stacktrace, &match?({__MODULE__, _, _, _}, &1))
end
defp error!(query, message) do
raise Ecto.QueryError, message: message, query: query
end
defp error!(query, expr, message) do
raise Ecto.QueryError, message: message, query: query, file: expr.file, line: expr.line
end
end