Packages
ash
3.4.67
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
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.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
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.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
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.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
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-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/sort/sort.ex
defmodule Ash.Sort do
@moduledoc """
Utilities and types for sorting.
"""
@type sort_order ::
:asc | :desc | :asc_nils_first | :asc_nils_last | :desc_nils_first | :desc_nils_last
@type sort_item ::
String.t()
| atom
| {atom, sort_order}
| %Ash.Query.Calculation{}
| {%Ash.Query.Calculation{}, sort_order}
| {atom, {Keyword.t() | map, sort_order}}
@type t ::
list(sort_item)
| sort_item
alias Ash.Error.Query.UnsortableField
alias Ash.Error.Query.{InvalidSortOrder, NoSuchField}
@doc """
Builds an expression to be used in a sort statement.
For example:
```elixir
Ash.Query.sort(query, Ash.Sort.expr_sort(author.full_name, :string))
Ash.Query.sort(query, [{Ash.Sort.expr_sort(author.full_name, :string), :desc_nils_first}])
```
"""
@spec expr_sort(Ash.Expr.t(), Ash.Type.t() | nil) :: Ash.Expr.t()
defmacro expr_sort(expression, type \\ nil) do
quote generated: true do
require Ash.Expr
type = unquote(type)
{type, constraints} =
case type do
{:array, _} ->
{type, []}
{type, constraints} ->
{type, constraints}
type ->
{type, []}
nil ->
{nil, []}
end
type = type && Ash.Type.get_type(type)
case Ash.Query.Calculation.new(
:__expr_sort__,
Ash.Resource.Calculation.Expression,
[expr: Ash.Expr.expr(unquote(expression))],
type,
constraints
) do
{:ok, calc} -> calc
{:error, term} -> raise Ash.Error.to_ash_error(term)
end
end
end
@doc """
A utility for parsing sorts provided from external input. Only allows sorting on public fields.
The supported formats are:
### Sort Strings
A comma separated list of fields to sort on, each with an optional prefix.
The prefixes are:
* "+" - Same as no prefix. Sorts `:asc`.
* "++" - Sorts `:asc_nils_first`
* "-" - Sorts `:desc`
* "--" - Sorts `:desc_nils_last`
For example
"foo,-bar,++baz,--buz"
### A list of sort strings
Same prefix rules as above, but provided as a list.
For example:
["foo", "-bar", "++baz", "--buz"]
## Handling specific values
A handler function may be provided that takes a string, and returns the relevant sort
It won't be given any prefixes, only the field. This allows for things like parsing the calculation values
out of the sort, or setting calculation values if they are not included in the sort string.
To return calculation parameters, return `{:field, %{param: :value}}`. This will end up as something
like `{:field, {%{param: :value}, :desc}}`, with the corresponding sort order.
This handler function will only be called if you pass in a string or list of strings for the sort.
Atoms will be assumed to have already been handled. The handler should return `nil` if it is not handling
the given field.
"""
@spec parse_input(
Ash.Resource.t(),
String.t()
| list(atom | String.t() | {atom, sort_order()} | list(String.t()))
| nil,
nil | (String.t() -> nil | atom | {atom, map})
) ::
{:ok, Ash.Sort.t()} | {:error, term}
def parse_input(resource, sort, handler \\ nil)
def parse_input(_, "", _), do: {:ok, []}
def parse_input(_, [], _), do: {:ok, []}
def parse_input(resource, sort, handler) when is_binary(sort) do
sort = String.split(sort, ",")
parse_input(resource, sort, handler)
end
def parse_input(resource, sort, handler) when is_list(sort) do
sort
|> Enum.reduce_while({:ok, []}, fn field, {:ok, sort} ->
case parse_sort(resource, field, handler) do
{:ok, value} -> {:cont, {:ok, [value | sort]}}
{:error, error} -> {:halt, {:error, error}}
end
end)
|> case do
{:ok, values} -> {:ok, Enum.reverse(values)}
{:error, error} -> {:error, error}
end
end
def parse_input(_resource, nil, _), do: {:ok, nil}
@doc """
Same as `parse_input/2` except raises any errors
See `parse_input/2` for more.
"""
def parse_input!(resource, sort, handler \\ nil) do
case parse_input(resource, sort, handler) do
{:ok, sort} ->
sort
{:error, error} ->
raise Ash.Error.to_error_class(error)
end
end
def parse_sort(resource, sort, handler \\ nil, public_only? \\ true)
def parse_sort(resource, {field, {input, direction}}, handler, public_only?)
when direction in [
:asc,
:desc,
:asc_nils_first,
:asc_nils_last,
:desc_nils_first,
:desc_nils_last
] do
case get_field(resource, field, handler, public_only?, input) do
{:error, error} -> {:error, error}
{:ok, field} -> {:ok, add_order(field, direction)}
end
end
def parse_sort(resource, {field, direction}, handler, public_only?)
when direction in [
:asc,
:desc,
:asc_nils_first,
:asc_nils_last,
:desc_nils_first,
:desc_nils_last
] do
case get_field(resource, field, handler, public_only?) do
{:error, error} -> {:error, error}
{:ok, field} -> {:ok, add_order(field, direction)}
end
end
def parse_sort(resource, {field, input}, handler, public_only?) when not is_atom(input) do
case get_field(resource, field, handler, public_only?, input) do
{:error, error} -> {:error, error}
{:ok, field} -> {:ok, add_order(field, :asc)}
end
end
def parse_sort(_resource, {_field, order}, _, _) do
{:error, InvalidSortOrder.exception(order: order)}
end
def parse_sort(resource, "++" <> field, handler, public_only?) do
case get_field(resource, field, handler, public_only?) do
{:error, error} -> {:error, error}
{:ok, field} -> {:ok, add_order(field, :asc_nils_first)}
end
end
def parse_sort(resource, "--" <> field, handler, public_only?) do
case get_field(resource, field, handler, public_only?) do
{:error, error} -> {:error, error}
{:ok, field} -> {:ok, add_order(field, :desc_nils_last)}
end
end
def parse_sort(resource, "+" <> field, handler, public_only?) do
case get_field(resource, field, handler, public_only?) do
{:error, error} -> {:error, error}
{:ok, field} -> {:ok, add_order(field, :asc)}
end
end
def parse_sort(resource, "-" <> field, handler, public_only?) do
case get_field(resource, field, handler, public_only?) do
{:error, error} -> {:error, error}
{:ok, field} -> {:ok, add_order(field, :desc)}
end
end
def parse_sort(resource, field, handler, public_only?) do
case get_field(resource, field, handler, public_only?) do
{:error, error} -> {:error, error}
{:ok, field} -> {:ok, add_order(field, :asc)}
end
end
defp add_order({field, map}, order) when is_map(map) do
{field, {map, order}}
end
defp add_order(field, order) do
{field, order}
end
defp get_field(resource, field, handler, only_public?, input \\ %{}) do
case call_handler(field, handler) do
nil ->
{path, field} =
cond do
is_binary(field) ->
case Enum.reverse(String.split(field, ".", trim: true)) do
[] -> {[], ""}
[field | path] -> {Enum.reverse(path), field}
end
is_atom(field) ->
case Enum.reverse(String.split(to_string(field), ".", trim: true)) do
[] -> {[], ""}
[_] -> {[], field}
[field | path] -> {Enum.reverse(path), field}
end
true ->
{[], field}
end
case path do
[] ->
case do_get_field(resource, field, only_public?, input) do
{:ok, field} ->
if type_sortable?(resource, field) do
case field do
%Ash.Query.Aggregate{} = field -> {:ok, field}
%Ash.Query.Calculation{} = field -> {:ok, field}
%{name: name} -> {:ok, name}
end
else
{:error, UnsortableField.exception(name: field.name, resource: resource)}
end
{:error, error} ->
{:error, error}
end
path ->
case related_field(resource, path, field, only_public?, input) do
{:ok, path, field, type, constraints} ->
case field do
%struct{} = thing when struct in [Ash.Query.Aggregate, Ash.Query.Calculation] ->
ref = %Ash.Query.Ref{
attribute: thing,
relationship_path: path,
resource: Ash.Resource.Info.related(resource, path)
}
Ash.Query.Calculation.new(
:__expr_sort__,
Ash.Resource.Calculation.Expression,
[expr: ref],
type,
constraints
)
%{name: name} ->
ref = %Ash.Query.Ref{
attribute: name,
relationship_path: path,
resource: Ash.Resource.Info.related(resource, path)
}
Ash.Query.Calculation.new(
:__expr_sort__,
Ash.Resource.Calculation.Expression,
[expr: ref],
type,
constraints
)
end
{:error, error} ->
{:error, error}
end
end
{:error, error} ->
{:error, error}
{:ok, ok} ->
{:ok, ok}
value ->
{:ok, value}
end
end
defp do_get_field(resource, field, true, input) do
case Ash.Resource.Info.public_field(resource, field) do
%Ash.Resource.Calculation{} = calc ->
with {:ok, calc} <-
Ash.Query.Calculation.from_resource_calculation(resource, calc, args: input) do
{:ok, %{calc | name: :__expr_sort__}}
end
nil ->
{:error, NoSuchField.exception(field: field, resource: resource)}
other ->
{:ok, other}
end
end
defp do_get_field(_resource, %Ash.Query.Calculation{} = calc, _, _input) do
{:ok, calc}
end
defp do_get_field(_resource, %Ash.Query.Aggregate{} = agg, _, _input) do
{:ok, agg}
end
defp do_get_field(resource, field, _, input) do
case Ash.Resource.Info.field(resource, field) do
%Ash.Resource.Calculation{} = calc ->
with {:ok, calc} <-
Ash.Query.Calculation.from_resource_calculation(resource, calc, args: input) do
{:ok, %{calc | name: :__expr_sort__}}
end
nil ->
{:error, NoSuchField.exception(field: field, resource: resource)}
other ->
{:ok, other}
end
end
defp do_get_relationship(resource, relationship, true) do
Ash.Resource.Info.public_relationship(resource, relationship)
end
defp do_get_relationship(resource, relationship, _) do
Ash.Resource.Info.relationship(resource, relationship)
end
defp type_sortable?(resource, %Ash.Resource.Aggregate{} = aggregate) do
attribute =
if aggregate.field do
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)
Ash.Resource.Info.attribute(related, aggregate.field)
end
attribute_type =
if attribute do
attribute.type
end
attribute_constraints =
if attribute do
attribute.constraints
end
case Ash.Query.Aggregate.kind_to_type(aggregate.kind, attribute_type, attribute_constraints) do
{:ok, type, constraints} ->
do_type_sortable?(resource, type, constraints)
_other ->
false
end
end
# an ugly workaround
defp type_sortable?(_resource, %Ash.Query.Calculation{}), do: true
defp type_sortable?(resource, field) do
do_type_sortable?(resource, field.type, field.constraints)
end
defp get_type(resource, %Ash.Resource.Aggregate{} = aggregate) do
attribute =
if aggregate.field do
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)
Ash.Resource.Info.attribute(related, aggregate.field)
end
attribute_type =
if attribute do
attribute.type
end
attribute_constraints =
if attribute do
attribute.constraints
end
case Ash.Query.Aggregate.kind_to_type(aggregate.kind, attribute_type, attribute_constraints) do
{:ok, type, constraints} ->
{type, constraints}
_other ->
nil
end
end
defp get_type(_resource, field) do
{field.type, field.constraints}
end
defp do_type_sortable?(resource, type, constraints) do
if Ash.Type.embedded_type?(type) do
false
else
Ash.DataLayer.data_layer_can?(
resource,
{:sort, Ash.Type.storage_type(type, constraints)}
)
end
end
defp call_handler(field, handler) when is_binary(field) and is_function(handler, 1) do
handler.(field)
end
defp call_handler(_, _), do: nil
defp related_field(resource, path, field, only_public?, input, acc \\ [])
defp related_field(resource, [], field, only_public?, input, acc) do
case do_get_field(resource, field, only_public?, input) do
{:error, error} ->
{:error, error}
{:ok, nil} ->
{:error, NoSuchField.exception(field: field, resource: resource)}
{:ok, %{sortable?: true} = field} ->
{type, constraints} = get_type(resource, field)
if do_type_sortable?(resource, type, constraints) do
{:ok, Enum.reverse(acc), field, type, constraints}
else
{:error, UnsortableField.exception(name: field.name, resource: resource)}
end
{:ok, %{name: name}} ->
{:error, UnsortableField.exception(name: name, resource: resource)}
end
end
defp related_field(resource, [first | rest], field, only_public?, input, acc) do
case do_get_relationship(resource, first, only_public?) do
%{sortable?: false, name: name} ->
{:error, UnsortableField.exception(name: name, resource: resource)}
%{sortable?: true, destination: destination, name: name} ->
related_field(destination, rest, field, only_public?, input, [name | acc])
_ ->
{:error, NoSuchField.exception(field: field, resource: resource)}
end
end
@doc """
Reverses an Ash sort statement.
"""
def reverse(sort) when is_list(sort) do
Enum.map(sort, &reverse/1)
end
def reverse(sort) when is_atom(sort) do
reverse({sort, :asc})
end
def reverse({key, {order, args}}) do
{key, {reverse_order(order), args}}
end
def reverse({key, order}) do
{key, reverse_order(order)}
end
defp reverse_order(:asc), do: :desc
defp reverse_order(:desc), do: :asc
defp reverse_order(:asc_nils_last), do: :desc_nils_first
defp reverse_order(:asc_nils_first), do: :desc_nils_last
defp reverse_order(:desc_nils_first), do: :asc_nils_last
defp reverse_order(:desc_nils_last), do: :asc_nils_first
@doc """
A utility for sorting a list of records at runtime.
For example:
Ash.Sort.runtime_sort([record1, record2, record3], name: :asc, type: :desc_nils_last)
Keep in mind that it is unrealistic to expect this runtime sort to always
be exactly the same as a sort that may have been applied by your data layer.
This is especially true for strings. For example, `Postgres` strings have a
collation that affects their sorting, making it unpredictable from the perspective
of a tool using the database: https://www.postgresql.org/docs/current/collation.html
"""
defdelegate runtime_sort(results, sort, domain \\ nil), to: Ash.Actions.Sort
end