Packages
ecto
2.0.0-rc.5
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
# Out means the given type must be taken out of an array
# It is the opposite of "left in right" in the query API.
defp type_for_key(:push, type), do: {:out, type}
defp type_for_key(:pull, type), do: {:out, type}
defp type_for_key(_, type), do: type
end