Current section
Files
Jump to
Current section
Files
lib/ecto/query/builder/dynamic.ex
import Kernel, except: [apply: 2]
defmodule Ecto.Query.Builder.Dynamic do
@moduledoc false
alias Ecto.Query.Builder
@doc """
Builds a dynamic expression.
"""
@spec build([Macro.t], Macro.t, Macro.Env.t) :: Macro.t
def build(binding, expr, env) do
{query, vars} = Builder.escape_binding(quote(do: query), binding, env)
{expr, {params, subqueries}} = Builder.escape(expr, :any, {[], []}, vars, env)
params = Builder.escape_params(params)
quote do
%Ecto.Query.DynamicExpr{fun: fn query ->
_ = unquote(query)
{unquote(expr), unquote(params), unquote(subqueries)}
end,
binding: unquote(Macro.escape(binding)),
file: unquote(env.file),
line: unquote(env.line)}
end
end
@doc """
Expands a dynamic expression for insertion into the given query.
"""
def fully_expand(query, %{file: file, line: line, binding: binding} = dynamic) do
{expr, {binding, params, subqueries, _count}} = expand(query, dynamic, {binding, [], [], 0})
{expr, binding, Enum.reverse(params), Enum.reverse(subqueries), file, line}
end
@doc """
Expands a dynamic expression as part of an existing expression.
Any dynamic expression parameter is prepended and the parameters
list is not reversed. This is useful when the dynamic expression
is given in the middle of an expression.
"""
def partially_expand(kind, query, %{binding: binding} = dynamic, params, count) do
{expr, {_binding, params, subqueries, count}} = expand(query, dynamic, {binding, params, [], count})
if subqueries != [] do
raise ArgumentError, "subqueries are not allowed in `#{kind}` expressions"
end
{expr, params, count}
end
defp expand(query, %{fun: fun}, {binding, params, subqueries, count}) do
{dynamic_expr, dynamic_params, dynamic_subqueries} = fun.(query)
Macro.postwalk(dynamic_expr, {binding, params, subqueries, count}, fn
{:^, meta, [ix]}, {binding, params, subqueries, count} ->
case Enum.fetch!(dynamic_params, ix) do
{%Ecto.Query.DynamicExpr{binding: new_binding} = dynamic, _} ->
binding = if length(new_binding) > length(binding), do: new_binding, else: binding
expand(query, dynamic, {binding, params, subqueries, count})
param ->
{{:^, meta, [count]}, {binding, [param | params], subqueries, count + 1}}
end
{:subquery, i}, {binding, params, subqueries, count} ->
subquery = Enum.fetch!(dynamic_subqueries, i)
ix = length(subqueries)
{{:subquery, ix}, {binding, [{:subquery, ix} | params], [subquery | subqueries], count + 1}}
expr, acc ->
{expr, acc}
end)
end
end