dynamic.ex (3983B)
1 import Kernel, except: [apply: 2] 2 3 defmodule Ecto.Query.Builder.Dynamic do 4 @moduledoc false 5 6 alias Ecto.Query.Builder 7 alias Ecto.Query.Builder.Select 8 9 @doc """ 10 Builds a dynamic expression. 11 """ 12 @spec build([Macro.t], Macro.t, Macro.Env.t) :: Macro.t 13 def build(binding, expr, env) do 14 {query, vars} = Builder.escape_binding(quote(do: query), binding, env) 15 {expr, {params, acc}} = escape(expr, {[], %{subqueries: [], aliases: %{}}}, vars, env) 16 aliases = Builder.escape_select_aliases(acc.aliases) 17 params = Builder.escape_params(params) 18 19 quote do 20 %Ecto.Query.DynamicExpr{fun: fn query -> 21 _ = unquote(query) 22 {unquote(expr), unquote(params), unquote(Enum.reverse(acc.subqueries)), unquote(aliases)} 23 end, 24 binding: unquote(Macro.escape(binding)), 25 file: unquote(env.file), 26 line: unquote(env.line)} 27 end 28 end 29 30 defp escape({:selected_as, _, [_, _]} = expr, _params_acc, vars, env) do 31 Select.escape(expr, vars, env) 32 end 33 34 defp escape(expr, params_acc, vars, env) do 35 Builder.escape(expr, :any, params_acc, vars, {env, &escape_expansion/5}) 36 end 37 38 defp escape_expansion(expr, _type, params_acc, vars, env) do 39 escape(expr, params_acc, vars, env) 40 end 41 42 @doc """ 43 Expands a dynamic expression for insertion into the given query. 44 """ 45 def fully_expand(query, %{file: file, line: line, binding: binding} = dynamic) do 46 {expr, {binding, params, subqueries, _aliases, _count}} = expand(query, dynamic, {binding, [], [], %{}, 0}) 47 {expr, binding, Enum.reverse(params), Enum.reverse(subqueries), file, line} 48 end 49 50 @doc """ 51 Expands a dynamic expression as part of an existing expression. 52 53 Any dynamic expression parameter is prepended and the parameters 54 list is not reversed. This is useful when the dynamic expression 55 is given in the middle of an expression. 56 """ 57 def partially_expand(query, %{binding: binding} = dynamic, params, subqueries, aliases, count) do 58 {expr, {_binding, params, subqueries, aliases, count}} = 59 expand(query, dynamic, {binding, params, subqueries, aliases, count}) 60 61 {expr, params, subqueries, aliases, count} 62 end 63 64 def partially_expand(kind, query, %{binding: binding} = dynamic, params, count) do 65 {expr, {_binding, params, subqueries, _aliases, count}} = 66 expand(query, dynamic, {binding, params, [], %{}, count}) 67 68 if subqueries != [] do 69 raise ArgumentError, "subqueries are not allowed in `#{kind}` expressions" 70 end 71 72 {expr, params, count} 73 end 74 75 defp expand(query, %{fun: fun}, {binding, params, subqueries, aliases, count}) do 76 {dynamic_expr, dynamic_params, dynamic_subqueries, dynamic_aliases} = fun.(query) 77 aliases = merge_aliases(aliases, dynamic_aliases) 78 79 Macro.postwalk(dynamic_expr, {binding, params, subqueries, aliases, count}, fn 80 {:^, meta, [ix]}, {binding, params, subqueries, aliases, count} -> 81 case Enum.fetch!(dynamic_params, ix) do 82 {%Ecto.Query.DynamicExpr{binding: new_binding} = dynamic, _} -> 83 binding = if length(new_binding) > length(binding), do: new_binding, else: binding 84 expand(query, dynamic, {binding, params, subqueries, aliases, count}) 85 86 param -> 87 {{:^, meta, [count]}, {binding, [param | params], subqueries, aliases, count + 1}} 88 end 89 90 {:subquery, i}, {binding, params, subqueries, aliases, count} -> 91 subquery = Enum.fetch!(dynamic_subqueries, i) 92 ix = length(subqueries) 93 {{:subquery, ix}, {binding, [{:subquery, ix} | params], [subquery | subqueries], aliases, count + 1}} 94 95 expr, acc -> 96 {expr, acc} 97 end) 98 end 99 100 defp merge_aliases(old_aliases, new_aliases) do 101 Enum.reduce(new_aliases, old_aliases, fn {alias, _}, aliases -> 102 Builder.add_select_alias(aliases, alias) 103 end) 104 end 105 end