Packages
ecto
3.9.0
3.14.1
3.14.0
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.2
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.4
3.8.3
3.8.2
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.1
3.0.0-rc.0
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.2.0-rc.1
2.2.0-rc.0
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.1.0-rc.5
2.1.0-rc.4
2.1.0-rc.3
2.1.0-rc.2
2.1.0-rc.1
2.1.0-rc.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-beta.2
2.0.0-beta.1
2.0.0-beta.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.12.0-rc
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
A toolkit for data mapping and language integrated query for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/query/builder/order_by.ex
import Kernel, except: [apply: 2]
defmodule Ecto.Query.Builder.OrderBy do
@moduledoc false
alias Ecto.Query.Builder
@directions [
:asc,
:asc_nulls_last,
:asc_nulls_first,
:desc,
:desc_nulls_last,
:desc_nulls_first
]
@doc """
Returns `true` if term is a valid order_by direction; otherwise returns `false`.
## Examples
iex> valid_direction?(:asc)
true
iex> valid_direction?(:desc)
true
iex> valid_direction?(:invalid)
false
"""
def valid_direction?(term), do: term in @directions
@doc """
Escapes an order by query.
The query is escaped to a list of `{direction, expression}`
pairs at runtime. Escaping also validates direction is one of
`:asc`, `:asc_nulls_last`, `:asc_nulls_first`, `:desc`,
`:desc_nulls_last` or `:desc_nulls_first`.
## Examples
iex> escape(:order_by, quote do [x.x, desc: 13] end, {[], %{}}, [x: 0], __ENV__)
{[asc: {:{}, [], [{:{}, [], [:., [], [{:{}, [], [:&, [], [0]]}, :x]]}, [], []]},
desc: 13],
{[], %{}}}
"""
@spec escape(:order_by | :distinct, Macro.t, {list, term}, Keyword.t, Macro.Env.t) ::
{Macro.t, {list, term}}
def escape(kind, expr, params_acc, vars, env) do
expr
|> List.wrap
|> Enum.map_reduce(params_acc, &do_escape(&1, &2, kind, vars, env))
end
defp do_escape({dir, {:^, _, [expr]}}, params_acc, kind, _vars, _env) do
{{quoted_dir!(kind, dir), quote(do: Ecto.Query.Builder.OrderBy.field!(unquote(kind), unquote(expr)))}, params_acc}
end
defp do_escape({:^, _, [expr]}, params_acc, kind, _vars, _env) do
{{:asc, quote(do: Ecto.Query.Builder.OrderBy.field!(unquote(kind), unquote(expr)))}, params_acc}
end
defp do_escape({dir, field}, params_acc, kind, _vars, _env) when is_atom(field) do
{{quoted_dir!(kind, dir), Macro.escape(to_field(field))}, params_acc}
end
defp do_escape(field, params_acc, _kind, _vars, _env) when is_atom(field) do
{{:asc, Macro.escape(to_field(field))}, params_acc}
end
defp do_escape({dir, expr}, params_acc, kind, vars, env) do
{ast, params_acc} = Builder.escape(expr, :any, params_acc, vars, env)
{{quoted_dir!(kind, dir), ast}, params_acc}
end
defp do_escape(expr, params_acc, _kind, vars, env) do
{ast, params_acc} = Builder.escape(expr, :any, params_acc, vars, env)
{{:asc, ast}, params_acc}
end
@doc """
Checks the variable is a quoted direction at compilation time or
delegate the check to runtime for interpolation.
"""
def quoted_dir!(kind, {:^, _, [expr]}),
do: quote(do: Ecto.Query.Builder.OrderBy.dir!(unquote(kind), unquote(expr)))
def quoted_dir!(_kind, dir) when dir in @directions,
do: dir
def quoted_dir!(kind, other) do
Builder.error!(
"expected #{Enum.map_join(@directions, ", ", &inspect/1)} or interpolated value " <>
"in `#{kind}`, got: `#{inspect other}`"
)
end
@doc """
Called by at runtime to verify the direction.
"""
def dir!(_kind, dir) when dir in @directions,
do: dir
def dir!(kind, other) do
raise ArgumentError,
"expected one of #{Enum.map_join(@directions, ", ", &inspect/1)} " <>
"in `#{kind}`, got: `#{inspect other}`"
end
@doc """
Called at runtime to verify a field.
"""
def field!(_kind, field) when is_atom(field) do
to_field(field)
end
def field!(kind, %Ecto.Query.DynamicExpr{} = dynamic_expression) do
raise ArgumentError,
"expected a field as an atom in `#{kind}`, got: `#{inspect dynamic_expression}`. " <>
"To use dynamic expressions, you need to interpolate at root level, as in: " <>
"`^[asc: dynamic, desc: :id]`"
end
def field!(kind, other) do
raise ArgumentError, "expected a field as an atom in `#{kind}`, got: `#{inspect other}`"
end
defp to_field(field), do: {{:., [], [{:&, [], [0]}, field]}, [], []}
@doc """
Shared between order_by and distinct.
"""
def order_by_or_distinct!(kind, query, exprs, params) do
{expr, {params, _}} =
Enum.map_reduce(List.wrap(exprs), {params, length(params)}, fn
{dir, expr}, params_count when dir in @directions ->
{expr, params} = dynamic_or_field!(kind, expr, query, params_count)
{{dir, expr}, params}
expr, params_count ->
{expr, params} = dynamic_or_field!(kind, expr, query, params_count)
{{:asc, expr}, params}
end)
{expr, params}
end
@doc """
Called at runtime to assemble order_by.
"""
def order_by!(query, exprs, file, line) do
{expr, params} = order_by_or_distinct!(:order_by, query, exprs, [])
expr = %Ecto.Query.QueryExpr{expr: expr, params: Enum.reverse(params), line: line, file: file}
apply(query, expr)
end
defp dynamic_or_field!(kind, %Ecto.Query.DynamicExpr{} = dynamic, query, {params, count}) do
{expr, params, count} = Builder.Dynamic.partially_expand(kind, query, dynamic, params, count)
{expr, {params, count}}
end
defp dynamic_or_field!(_kind, field, _query, params_count) when is_atom(field) do
{to_field(field), params_count}
end
defp dynamic_or_field!(kind, other, _query, _params_count) do
raise ArgumentError,
"`#{kind}` interpolated on root expects a field or a keyword list " <>
"with the direction as keys and fields or dynamics as values, got: `#{inspect other}`"
end
@doc """
Builds a quoted expression.
The quoted expression should evaluate to a query at runtime.
If possible, it does all calculations at compile time to avoid
runtime work.
"""
@spec build(Macro.t, [Macro.t], Macro.t, Macro.Env.t) :: Macro.t
def build(query, _binding, {:^, _, [var]}, env) do
quote do
Ecto.Query.Builder.OrderBy.order_by!(unquote(query), unquote(var), unquote(env.file), unquote(env.line))
end
end
def build(query, binding, expr, env) do
{query, binding} = Builder.escape_binding(query, binding, env)
{expr, {params, _acc}} = escape(:order_by, expr, {[], %{}}, binding, env)
params = Builder.escape_params(params)
order_by = quote do: %Ecto.Query.QueryExpr{
expr: unquote(expr),
params: unquote(params),
file: unquote(env.file),
line: unquote(env.line)}
Builder.apply_query(query, __MODULE__, [order_by], env)
end
@doc """
The callback applied by `build/4` to build the query.
"""
@spec apply(Ecto.Queryable.t, term) :: Ecto.Query.t
def apply(%Ecto.Query{order_bys: order_bys} = query, expr) do
%{query | order_bys: order_bys ++ [expr]}
end
def apply(query, expr) do
apply(Ecto.Queryable.to_query(query), expr)
end
end