Packages

ash

2.4.15
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
ash lib ash actions sort.ex
Raw

lib/ash/actions/sort.ex

defmodule Ash.Actions.Sort do
@moduledoc false
alias Ash.Error.Query.{
AggregatesNotSupported,
InvalidSortOrder,
NoSuchAttribute,
UnsortableAttribute
}
@sort_orders [:asc, :desc, :asc_nils_first, :asc_nils_last, :desc_nils_first, :desc_nils_last]
def process(_resource, empty, _aggregates, context \\ %{})
def process(_resource, empty, _aggregates, _context) when empty in [nil, []], do: {:ok, []}
def process(resource, sort, aggregates, context) when is_list(sort) do
sort
|> Enum.map(fn
{key, {order, context}} when is_atom(order) ->
{key, {order, context}}
{key, val} ->
if is_atom(val) do
{key, val}
else
{key, {:asc, val}}
end
end)
|> Enum.reduce({[], []}, fn
{field, {inner_order, _} = order}, {sorts, errors} when inner_order in @sort_orders ->
case Ash.Resource.Info.calculation(resource, field) do
nil ->
{sorts,
[
"Cannot provide context to a non-calculation field while sorting"
| errors
]}
calc ->
{module, opts} = calc.calculation
Code.ensure_compiled!(module)
if function_exported?(module, :expression, 2) do
if Ash.DataLayer.data_layer_can?(resource, :expression_calculation_sort) do
calculation_sort(
field,
calc,
module,
opts,
calc.type,
order,
sorts,
errors,
context
)
else
{sorts, ["Datalayer cannot sort on calculations"]}
end
else
{sorts, ["Calculations cannot be sorted on unless they define an expression"]}
end
end
{%Ash.Query.Calculation{} = calc, order}, {sorts, errors} ->
if order in @sort_orders do
{sorts ++ [{calc, order}], errors}
else
{sorts, [InvalidSortOrder.exception(order: order) | errors]}
end
{field, order}, {sorts, errors} when order in @sort_orders ->
attribute = Ash.Resource.Info.attribute(resource, field)
cond do
aggregate = Ash.Resource.Info.aggregate(resource, field) ->
aggregate_sort(aggregates, aggregate, order, resource, sorts, errors)
Map.has_key?(aggregates, field) ->
aggregate_sort(aggregates, field, order, resource, sorts, errors)
calc = Ash.Resource.Info.calculation(resource, field) ->
{module, opts} = calc.calculation
Code.ensure_compiled(module)
if :erlang.function_exported(module, :expression, 2) do
if Ash.DataLayer.data_layer_can?(resource, :expression_calculation_sort) do
calculation_sort(
field,
calc,
module,
opts,
calc.type,
order,
sorts,
errors,
context
)
else
{sorts, ["Datalayer cannot sort on calculations"]}
end
else
{sorts, ["Calculations cannot be sorted on unless they define an expression"]}
end
!attribute ->
{sorts, [NoSuchAttribute.exception(name: field, resource: resource) | errors]}
Ash.Type.embedded_type?(attribute.type) ->
{sorts, ["Cannot sort on embedded types" | errors]}
match?({:array, _}, attribute.type) ->
{sorts, ["Cannot sort on array types" | errors]}
!Ash.DataLayer.data_layer_can?(
resource,
{:sort, Ash.Type.storage_type(attribute.type)}
) ->
{sorts,
[
UnsortableAttribute.exception(field: field)
| errors
]}
true ->
{sorts ++ [{field, order}], errors}
end
{_, order}, {sorts, errors} ->
{sorts, [InvalidSortOrder.exception(order: order) | errors]}
end)
|> case do
{sorts, []} -> {:ok, sorts}
{_, errors} -> {:error, errors}
end
end
def sorting_on_identity?(%{sort: nil}), do: false
def sorting_on_identity?(query) do
identity_keys =
query.resource
|> Ash.Resource.Info.identities()
|> Enum.map(& &1.keys)
Enum.any?([Ash.Resource.Info.primary_key(query.resource) | identity_keys], fn keyset ->
Enum.all?(keyset, fn key ->
Enum.any?(query.sort, fn
{sort, _} when is_atom(sort) ->
sort == key
_ ->
false
end)
end)
end)
end
defp aggregate_sort(aggregates, field, order, resource, sorts, errors) do
{field, type} =
case field do
field when is_atom(field) ->
aggregate = Map.get(aggregates, field)
{field, {:ok, aggregate.type}}
%Ash.Resource.Aggregate{} = agg ->
attribute_type =
if agg.field do
related = Ash.Resource.Info.related(resource, agg.relationship_path)
Ash.Resource.Info.attribute(related, agg.field).type
end
{agg.name, Ash.Query.Aggregate.kind_to_type(agg.kind, attribute_type)}
end
case type do
{:ok, type} ->
if Ash.DataLayer.data_layer_can?(resource, :aggregate_sort) &&
Ash.DataLayer.data_layer_can?(
resource,
{:sort, Ash.Type.storage_type(type)}
) do
{sorts ++ [{field, order}], errors}
else
{sorts,
[AggregatesNotSupported.exception(resource: resource, feature: "sorting") | errors]}
end
{:error, error} ->
{sorts, [error | errors]}
end
end
defp calculation_sort(field, calc, module, opts, type, order, sorts, errors, context) do
{order, calc_context} =
case order do
order when is_atom(order) ->
{order, %{}}
{order, value} when is_list(value) ->
{order, Map.new(value)}
{order, value} when is_map(value) ->
{order, value}
other ->
{other, %{}}
end
with {:ok, input} <- Ash.Query.validate_calculation_arguments(calc, calc_context),
{:ok, calc} <-
Ash.Query.Calculation.new(
field,
module,
opts,
type,
Map.put(input, :context, context),
calc.filterable?,
calc.load
) do
calc = Map.put(calc, :load, field)
{sorts ++ [{calc, order}], errors}
else
{:error, error} ->
{sorts, [error | errors]}
end
end
def runtime_sort([], _empty), do: []
def runtime_sort(results, empty) when empty in [nil, []], do: results
def runtime_sort([%resource{} | _] = results, [{field, direction}]) do
sort_by(results, &resolve_field(&1, field, resource), direction)
end
def runtime_sort([%resource{} | _] = results, [{field, direction} | rest]) do
results
|> Enum.group_by(&resolve_field(&1, field, resource))
|> sort_by(fn {key, _value} -> key end, direction)
|> Enum.flat_map(fn {_, records} ->
runtime_sort(records, rest)
end)
end
defp resolve_field(record, %Ash.Query.Calculation{} = calc, resource) do
cond do
:erlang.function_exported(calc.module, :calculate, 3) ->
calc.module.calculate([record], calc.opts, calc.context)
:erlang.function_exported(calc.module, :expression, 2) ->
expression = calc.module.expression(calc.opts, calc.context)
case Ash.Filter.hydrate_refs(expression, %{
resource: resource,
aggregates: %{},
calculations: %{},
public?: false
}) do
{:ok, expression} ->
case Ash.Filter.Runtime.do_match(record, expression) do
{:ok, value} ->
{:ok, value}
_ ->
nil
end
_ ->
nil
end
true ->
nil
end
end
defp resolve_field(record, field, _resource) do
Map.get(record, field)
end
# :asc/:desc added to elixir in 1.10. sort_by and to_sort_by_fun copied from core
defp sort_by(enumerable, mapper, sorter) do
enumerable
|> Enum.map(&{&1, mapper.(&1)})
|> Enum.sort(to_sort_by_fun(sorter))
|> Enum.map(&elem(&1, 0))
end
defp to_sort_by_fun(sorter) when is_function(sorter, 2),
do: &sorter.(elem(&1, 1), elem(&2, 1))
defp to_sort_by_fun(:asc) do
fn
nil, nil ->
true
_, nil ->
true
nil, _ ->
false
x, y ->
Comp.less_or_equal?(elem(x, 1), elem(y, 1))
end
end
defp to_sort_by_fun(:desc) do
fn
nil, nil ->
true
_, nil ->
false
nil, _ ->
true
x, y ->
Comp.greater_or_equal?(elem(x, 1), elem(y, 1))
end
end
defp to_sort_by_fun(:asc_nils_last) do
fn x, y ->
if is_nil(elem(x, 1)) && !is_nil(elem(y, 1)) do
false
else
Comp.less_or_equal?(elem(x, 1), elem(y, 1))
end
end
end
defp to_sort_by_fun(:asc_nils_first) do
fn x, y ->
if is_nil(elem(x, 1)) && !is_nil(elem(y, 1)) do
true
else
Comp.less_or_equal?(elem(x, 1), elem(y, 1))
end
end
end
defp to_sort_by_fun(:desc_nils_first) do
fn x, y ->
if is_nil(elem(x, 1)) && !is_nil(elem(y, 1)) do
true
else
Comp.greater_or_equal?(elem(x, 1), elem(y, 1))
end
end
end
defp to_sort_by_fun(:desc_nils_last) do
fn x, y ->
if is_nil(elem(x, 1)) && !is_nil(elem(y, 1)) do
false
else
Comp.greater_or_equal?(elem(x, 1), elem(y, 1))
end
end
end
defp to_sort_by_fun({direction, _input}) do
to_sort_by_fun(direction)
end
defp to_sort_by_fun(module) when is_atom(module),
do: &(module.compare(elem(&1, 1), elem(&2, 1)) != :gt)
defp to_sort_by_fun({:asc, module}) when is_atom(module),
do: &(module.compare(elem(&1, 1), elem(&2, 1)) != :gt)
defp to_sort_by_fun({:desc, module}) when is_atom(module),
do: &(module.compare(elem(&1, 1), elem(&2, 1)) != :lt)
end