cte.ex (2981B)
1 import Kernel, except: [apply: 3] 2 3 defmodule Ecto.Query.Builder.CTE do 4 @moduledoc false 5 6 alias Ecto.Query.Builder 7 8 @doc """ 9 Escapes the CTE name. 10 11 iex> escape(quote(do: "FOO"), __ENV__) 12 "FOO" 13 14 """ 15 @spec escape(Macro.t, Macro.Env.t) :: Macro.t 16 def escape(name, _env) when is_bitstring(name), do: name 17 18 def escape({:^, _, [expr]}, _env), do: expr 19 20 def escape(expr, env) do 21 case Macro.expand_once(expr, env) do 22 ^expr -> 23 Builder.error! "`#{Macro.to_string(expr)}` is not a valid CTE name. " <> 24 "It must be a literal string or an interpolated variable." 25 26 expr -> 27 escape(expr, env) 28 end 29 end 30 31 @doc """ 32 Builds a quoted expression. 33 34 The quoted expression should evaluate to a query at runtime. 35 If possible, it does all calculations at compile time to avoid 36 runtime work. 37 """ 38 @spec build(Macro.t, Macro.t, Macro.t, Macro.Env.t) :: Macro.t 39 def build(query, name, cte, env) do 40 Builder.apply_query(query, __MODULE__, [escape(name, env), build_cte(name, cte, env)], env) 41 end 42 43 @spec build_cte(Macro.t, Macro.t, Macro.Env.t) :: Macro.t 44 def build_cte(_name, {:^, _, [expr]}, _env) do 45 quote do: Ecto.Queryable.to_query(unquote(expr)) 46 end 47 48 def build_cte(_name, {:fragment, _, _} = fragment, env) do 49 {expr, {params, _acc}} = Builder.escape(fragment, :any, {[], %{}}, [], env) 50 params = Builder.escape_params(params) 51 52 quote do 53 %Ecto.Query.QueryExpr{ 54 expr: unquote(expr), 55 params: unquote(params), 56 file: unquote(env.file), 57 line: unquote(env.line) 58 } 59 end 60 end 61 62 def build_cte(name, cte, env) do 63 case Macro.expand_once(cte, env) do 64 ^cte -> 65 Builder.error! "`#{Macro.to_string(cte)}` is not a valid CTE (named: #{Macro.to_string(name)}). " <> 66 "The CTE must be an interpolated query, such as ^existing_query or a fragment." 67 68 cte -> 69 build_cte(name, cte, env) 70 end 71 end 72 73 @doc """ 74 The callback applied by `build/4` to build the query. 75 """ 76 @spec apply(Ecto.Queryable.t, bitstring, Ecto.Queryable.t) :: Ecto.Query.t 77 # Runtime 78 def apply(%Ecto.Query{with_ctes: with_expr} = query, name, %_{} = with_query) do 79 %{query | with_ctes: apply_cte(with_expr, name, with_query)} 80 end 81 82 # Compile 83 def apply(%Ecto.Query{with_ctes: with_expr} = query, name, with_query) do 84 update = quote do 85 Ecto.Query.Builder.CTE.apply_cte(unquote(with_expr), unquote(name), unquote(with_query)) 86 end 87 88 %{query | with_ctes: update} 89 end 90 91 # Runtime catch-all 92 def apply(query, name, with_query) do 93 apply(Ecto.Queryable.to_query(query), name, with_query) 94 end 95 96 @doc false 97 def apply_cte(nil, name, with_query) do 98 %Ecto.Query.WithExpr{queries: [{name, with_query}]} 99 end 100 101 def apply_cte(%Ecto.Query.WithExpr{queries: queries} = with_expr, name, with_query) do 102 %{with_expr | queries: List.keystore(queries, name, 0, {name, with_query})} 103 end 104 end