zf

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

order_by.ex (6710B)


      1 import Kernel, except: [apply: 2]
      2 
      3 defmodule Ecto.Query.Builder.OrderBy do
      4   @moduledoc false
      5 
      6   alias Ecto.Query.Builder
      7 
      8   @directions [
      9     :asc,
     10     :asc_nulls_last,
     11     :asc_nulls_first,
     12     :desc,
     13     :desc_nulls_last,
     14     :desc_nulls_first
     15   ]
     16 
     17   @doc """
     18   Returns `true` if term is a valid order_by direction; otherwise returns `false`.
     19 
     20   ## Examples
     21 
     22       iex> valid_direction?(:asc)
     23       true
     24 
     25       iex> valid_direction?(:desc)
     26       true
     27 
     28       iex> valid_direction?(:invalid)
     29       false
     30 
     31   """
     32   def valid_direction?(term), do: term in @directions
     33 
     34   @doc """
     35   Escapes an order by query.
     36 
     37   The query is escaped to a list of `{direction, expression}`
     38   pairs at runtime. Escaping also validates direction is one of
     39   `:asc`, `:asc_nulls_last`, `:asc_nulls_first`, `:desc`,
     40   `:desc_nulls_last` or `:desc_nulls_first`.
     41 
     42   ## Examples
     43 
     44       iex> escape(:order_by, quote do [x.x, desc: 13] end, {[], %{}}, [x: 0], __ENV__)
     45       {[asc: {:{}, [], [{:{}, [], [:., [], [{:{}, [], [:&, [], [0]]}, :x]]}, [], []]},
     46         desc: 13],
     47        {[], %{}}}
     48 
     49   """
     50   @spec escape(:order_by | :distinct, Macro.t, {list, term}, Keyword.t, Macro.Env.t) ::
     51           {Macro.t, {list, term}}
     52   def escape(kind, expr, params_acc, vars, env) do
     53     expr
     54     |> List.wrap
     55     |> Enum.map_reduce(params_acc, &do_escape(&1, &2, kind, vars, env))
     56   end
     57 
     58   defp do_escape({dir, {:^, _, [expr]}}, params_acc, kind, _vars, _env) do
     59     {{quoted_dir!(kind, dir), quote(do: Ecto.Query.Builder.OrderBy.field!(unquote(kind), unquote(expr)))}, params_acc}
     60   end
     61 
     62   defp do_escape({:^, _, [expr]}, params_acc, kind, _vars, _env) do
     63     {{:asc, quote(do: Ecto.Query.Builder.OrderBy.field!(unquote(kind), unquote(expr)))}, params_acc}
     64   end
     65 
     66   defp do_escape({dir, field}, params_acc, kind, _vars, _env) when is_atom(field) do
     67     {{quoted_dir!(kind, dir), Macro.escape(to_field(field))}, params_acc}
     68   end
     69 
     70   defp do_escape(field, params_acc, _kind, _vars, _env) when is_atom(field) do
     71     {{:asc, Macro.escape(to_field(field))}, params_acc}
     72   end
     73 
     74   defp do_escape({dir, expr}, params_acc, kind, vars, env) do
     75     {ast, params_acc} = Builder.escape(expr, :any, params_acc, vars, env)
     76     {{quoted_dir!(kind, dir), ast}, params_acc}
     77   end
     78 
     79   defp do_escape(expr, params_acc, _kind, vars, env) do
     80     {ast, params_acc} = Builder.escape(expr, :any, params_acc, vars, env)
     81     {{:asc, ast}, params_acc}
     82   end
     83 
     84   @doc """
     85   Checks the variable is a quoted direction at compilation time or
     86   delegate the check to runtime for interpolation.
     87   """
     88   def quoted_dir!(kind, {:^, _, [expr]}),
     89     do: quote(do: Ecto.Query.Builder.OrderBy.dir!(unquote(kind), unquote(expr)))
     90   def quoted_dir!(_kind, dir) when dir in @directions,
     91     do: dir
     92   def quoted_dir!(kind, other) do
     93     Builder.error!(
     94       "expected #{Enum.map_join(@directions, ", ", &inspect/1)} or interpolated value " <>
     95         "in `#{kind}`, got: `#{inspect other}`"
     96     )
     97   end
     98 
     99   @doc """
    100   Called by at runtime to verify the direction.
    101   """
    102   def dir!(_kind, dir) when dir in @directions,
    103     do: dir
    104 
    105   def dir!(kind, other) do
    106     raise ArgumentError,
    107       "expected one of #{Enum.map_join(@directions, ", ", &inspect/1)} " <>
    108         "in `#{kind}`, got: `#{inspect other}`"
    109   end
    110 
    111   @doc """
    112   Called at runtime to verify a field.
    113   """
    114   def field!(_kind, field) when is_atom(field) do
    115     to_field(field)
    116   end
    117 
    118   def field!(kind, %Ecto.Query.DynamicExpr{} = dynamic_expression) do
    119     raise ArgumentError,
    120       "expected a field as an atom in `#{kind}`, got: `#{inspect dynamic_expression}`. " <>
    121       "To use dynamic expressions, you need to interpolate at root level, as in: " <>
    122       "`^[asc: dynamic, desc: :id]`"
    123   end
    124 
    125   def field!(kind, other) do
    126     raise ArgumentError, "expected a field as an atom in `#{kind}`, got: `#{inspect other}`"
    127   end
    128 
    129   defp to_field(field), do: {{:., [], [{:&, [], [0]}, field]}, [], []}
    130 
    131   @doc """
    132   Shared between order_by and distinct.
    133   """
    134   def order_by_or_distinct!(kind, query, exprs, params) do
    135     {expr, {params, _}} =
    136       Enum.map_reduce(List.wrap(exprs), {params, length(params)}, fn
    137         {dir, expr}, params_count when dir in @directions ->
    138           {expr, params} = dynamic_or_field!(kind, expr, query, params_count)
    139           {{dir, expr}, params}
    140         expr, params_count ->
    141           {expr, params} = dynamic_or_field!(kind, expr, query, params_count)
    142           {{:asc, expr}, params}
    143       end)
    144 
    145     {expr, params}
    146   end
    147 
    148   @doc """
    149   Called at runtime to assemble order_by.
    150   """
    151   def order_by!(query, exprs, file, line) do
    152     {expr, params} = order_by_or_distinct!(:order_by, query, exprs, [])
    153     expr = %Ecto.Query.QueryExpr{expr: expr, params: Enum.reverse(params), line: line, file: file}
    154     apply(query, expr)
    155   end
    156 
    157   defp dynamic_or_field!(kind, %Ecto.Query.DynamicExpr{} = dynamic, query, {params, count}) do
    158     {expr, params, count} = Builder.Dynamic.partially_expand(kind, query, dynamic, params, count)
    159     {expr, {params, count}}
    160   end
    161 
    162   defp dynamic_or_field!(_kind, field, _query, params_count) when is_atom(field) do
    163     {to_field(field), params_count}
    164   end
    165 
    166   defp dynamic_or_field!(kind, other, _query, _params_count) do
    167     raise ArgumentError,
    168           "`#{kind}` interpolated on root expects a field or a keyword list " <>
    169             "with the direction as keys and fields or dynamics as values, got: `#{inspect other}`"
    170   end
    171 
    172   @doc """
    173   Builds a quoted expression.
    174 
    175   The quoted expression should evaluate to a query at runtime.
    176   If possible, it does all calculations at compile time to avoid
    177   runtime work.
    178   """
    179   @spec build(Macro.t, [Macro.t], Macro.t, Macro.Env.t) :: Macro.t
    180   def build(query, _binding, {:^, _, [var]}, env) do
    181     quote do
    182       Ecto.Query.Builder.OrderBy.order_by!(unquote(query), unquote(var), unquote(env.file), unquote(env.line))
    183     end
    184   end
    185 
    186   def build(query, binding, expr, env) do
    187     {query, binding} = Builder.escape_binding(query, binding, env)
    188     {expr, {params, _acc}} = escape(:order_by, expr, {[], %{}}, binding, env)
    189     params = Builder.escape_params(params)
    190 
    191     order_by = quote do: %Ecto.Query.QueryExpr{
    192                            expr: unquote(expr),
    193                            params: unquote(params),
    194                            file: unquote(env.file),
    195                            line: unquote(env.line)}
    196     Builder.apply_query(query, __MODULE__, [order_by], env)
    197   end
    198 
    199   @doc """
    200   The callback applied by `build/4` to build the query.
    201   """
    202   @spec apply(Ecto.Queryable.t, term) :: Ecto.Query.t
    203   def apply(%Ecto.Query{order_bys: order_bys} = query, expr) do
    204     %{query | order_bys: order_bys ++ [expr]}
    205   end
    206   def apply(query, expr) do
    207     apply(Ecto.Queryable.to_query(query), expr)
    208   end
    209 end