zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

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