Packages
ecto
1.1.6
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/update.ex
defmodule Ecto.Query.Builder.Update do
@moduledoc false
@keys [:set, :inc, :push, :pull]
alias Ecto.Query.Builder
@doc """
Escapes a list of quoted expressions.
iex> escape([], [], __ENV__)
{[], [], %{}}
iex> escape([set: []], [], __ENV__)
{[set: []], [], %{}}
iex> escape(quote(do: ^[set: []]), [], __ENV__)
{[], [set: []], %{}}
iex> escape(quote(do: [set: ^[foo: 1]]), [], __ENV__)
{[], [set: [foo: 1]], %{}}
iex> escape(quote(do: [set: [foo: ^1]]), [], __ENV__)
{[set: [foo: {:{}, [], [:^, [], [0]]}]], [], %{0 => {1, {0, :foo}}}}
"""
@spec escape(Macro.t, Keyword.t, Macro.Env.t) :: {Macro.t, Macro.t, %{}}
def escape(expr, vars, env) when is_list(expr) do
escape_op(expr, [], [], %{}, vars, env)
end
def escape({:^, _, [v]}, _vars, _env) do
{[], v, %{}}
end
def escape(expr, _vars, _env) do
compile_error!(expr)
end
defp escape_op([{k, v}|t], compile, runtime, params, vars, env) when is_atom(k) and is_list(v) do
validate_key!(k)
{v, params} = escape_field(k, v, params, vars, env)
escape_op(t, [{k, v}|compile], runtime, params, vars, env)
end
defp escape_op([{k, {:^, _, [v]}}|t], compile, runtime, params, vars, env) when is_atom(k) do
validate_key!(k)
escape_op(t, compile, [{k, v}|runtime], params, vars, env)
end
defp escape_op([], compile, runtime, params, _vars, _env) do
{Enum.reverse(compile), Enum.reverse(runtime), params}
end
defp escape_op(expr, _compile, _runtime, _params, _vars, _env) do
compile_error!(expr)
end
defp escape_field(key, kw, params, vars, env) do
Enum.map_reduce kw, params, fn
{k, v}, acc when is_atom(k) ->
{v, params} = Builder.escape(v, type_for_key(key, {0, k}), acc, vars, env)
{{k, v}, params}
_, _acc ->
Builder.error! "malformed #{inspect key} in update `#{Macro.to_string(kw)}`, " <>
"expected a keyword list"
end
end
defp compile_error!(expr) do
Builder.error! "malformed update `#{Macro.to_string(expr)}` in query expression, " <>
"expected a keyword list with lists or interpolated expressions as values"
end
@doc """
Builds a quoted expression.
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)
{compile, runtime, params} = escape(expr, binding, env)
query =
if compile == [] do
query
else
params = Builder.escape_params(params)
update = quote do
%Ecto.Query.QueryExpr{expr: unquote(compile), params: unquote(params),
file: unquote(env.file), line: unquote(env.line)}
end
Builder.apply_query(query, __MODULE__, [update], env)
end
query =
if runtime == [] do
query
else
update = quote do
Ecto.Query.Builder.Update.runtime(unquote(runtime), unquote(env.line), unquote(env.file))
end
Builder.apply_query(query, __MODULE__, [update], env)
end
query
end
@doc """
The callback applied by `build/4` to build the query.
"""
@spec apply(Ecto.Queryable.t, term) :: Ecto.Query.t
def apply(query, updates) do
query = Ecto.Queryable.to_query(query)
%{query | updates: query.updates ++ [updates]}
end
@doc """
If there are interpolated updates at compile time,
we need to handle them at runtime. We do such in
this callback.
"""
@spec runtime(term, line :: integer, file :: binary) :: Ecto.Query.t
def runtime(runtime, line, file) when is_list(runtime) do
{runtime, {params, _count}} =
Enum.map_reduce runtime, {[], 0}, fn
{k, v}, acc when is_atom(k) and is_list(v) ->
validate_key!(k)
{v, params} = runtime_field(k, v, acc)
{{k, v}, params}
_, _acc ->
runtime_error! runtime
end
%Ecto.Query.QueryExpr{expr: runtime, params: Enum.reverse(params),
file: file, line: line}
end
def runtime(runtime, _line, _file) do
runtime_error!(runtime)
end
defp runtime_field(key, kw, acc) do
Enum.map_reduce kw, acc, fn
{k, v}, {params, count} when is_atom(k) ->
params = [{v, type_for_key(key, {0, k})}|params]
{{k, {:^, [], [count]}}, {params, count+1}}
_, _acc ->
raise ArgumentError,
"malformed #{inspect key} in update `#{inspect(kw)}`, " <>
"expected a keyword list"
end
end
defp runtime_error!(value) do
raise ArgumentError,
"malformed update `#{inspect(value)}` in query expression, " <>
"expected a keyword list with lists or interpolated expressions as values"
end
defp validate_key!(key) when key in @keys, do: :ok
defp validate_key!(key) do
Builder.error! "unknown key `#{inspect(key)}` in update"
end
defp type_for_key(:push, type), do: {:in_array, type}
defp type_for_key(:pull, type), do: {:in_array, type}
defp type_for_key(_, type), do: type
end