distinct.ex (2676B)
1 import Kernel, except: [apply: 2] 2 3 defmodule Ecto.Query.Builder.Distinct do 4 @moduledoc false 5 6 alias Ecto.Query.Builder 7 8 @doc """ 9 Escapes a list of quoted expressions. 10 11 iex> escape(quote do true end, {[], %{}}, [], __ENV__) 12 {true, {[], %{}}} 13 14 iex> escape(quote do [x.x, 13] end, {[], %{}}, [x: 0], __ENV__) 15 {[asc: {:{}, [], [{:{}, [], [:., [], [{:{}, [], [:&, [], [0]]}, :x]]}, [], []]}, 16 asc: 13], 17 {[], %{}}} 18 19 """ 20 @spec escape(Macro.t, {list, term}, Keyword.t, Macro.Env.t) :: {Macro.t, {list, term}} 21 def escape(expr, params_acc, _vars, _env) when is_boolean(expr) do 22 {expr, params_acc} 23 end 24 25 def escape(expr, params_acc, vars, env) do 26 Builder.OrderBy.escape(:distinct, expr, params_acc, vars, env) 27 end 28 29 @doc """ 30 Called at runtime to verify distinct. 31 """ 32 def distinct!(query, distinct, file, line) when is_boolean(distinct) do 33 apply(query, %Ecto.Query.QueryExpr{expr: distinct, params: [], line: line, file: file}) 34 end 35 def distinct!(query, distinct, file, line) do 36 {expr, params} = Builder.OrderBy.order_by_or_distinct!(:distinct, query, distinct, []) 37 expr = %Ecto.Query.QueryExpr{expr: expr, params: Enum.reverse(params), line: line, file: file} 38 apply(query, expr) 39 end 40 41 @doc """ 42 Builds a quoted expression. 43 44 The quoted expression should evaluate to a query at runtime. 45 If possible, it does all calculations at compile time to avoid 46 runtime work. 47 """ 48 @spec build(Macro.t, [Macro.t], Macro.t, Macro.Env.t) :: Macro.t 49 def build(query, _binding, {:^, _, [var]}, env) do 50 quote do 51 Ecto.Query.Builder.Distinct.distinct!(unquote(query), unquote(var), unquote(env.file), unquote(env.line)) 52 end 53 end 54 55 def build(query, binding, expr, env) do 56 {query, binding} = Builder.escape_binding(query, binding, env) 57 {expr, {params, _acc}} = escape(expr, {[], %{}}, binding, env) 58 params = Builder.escape_params(params) 59 60 distinct = quote do: %Ecto.Query.QueryExpr{ 61 expr: unquote(expr), 62 params: unquote(params), 63 file: unquote(env.file), 64 line: unquote(env.line)} 65 Builder.apply_query(query, __MODULE__, [distinct], env) 66 end 67 68 @doc """ 69 The callback applied by `build/4` to build the query. 70 """ 71 @spec apply(Ecto.Queryable.t, term) :: Ecto.Query.t 72 def apply(%Ecto.Query{distinct: nil} = query, expr) do 73 %{query | distinct: expr} 74 end 75 def apply(%Ecto.Query{}, _expr) do 76 Builder.error! "only one distinct expression is allowed in query" 77 end 78 def apply(query, expr) do 79 apply(Ecto.Queryable.to_query(query), expr) 80 end 81 end