Packages
ash_postgres
1.5.15
2.10.0
2.9.1
2.9.0
2.8.0
2.7.0
2.6.32
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.12
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.22
2.5.21
2.5.20
2.5.19
2.5.18
2.5.17
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.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.1
2.3.0
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.19
2.1.18
2.1.17
2.1.15
2.1.14
2.1.13
2.1.12
2.1.11
2.1.10
2.1.9
2.1.8
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.12
2.0.11
2.0.10
2.0.9
2.0.8
2.0.7
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.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
1.5.30
1.5.29
1.5.28
1.5.27
1.5.26
1.5.25
1.5.24
1.5.23
1.5.22
1.5.21
1.5.20
1.5.19
1.5.18
1.5.17
1.5.16
1.5.15
1.5.14
1.5.13
1.5.12
1.5.11
1.5.10
1.5.9
1.5.8
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.0
1.3.68
1.3.67
1.3.66
1.3.65
1.3.64
1.3.63
1.3.62
1.3.61
1.3.60
1.3.59
1.3.58
1.3.56
1.3.55
1.3.54
1.3.53
1.3.52
1.3.51
1.3.50
1.3.49
1.3.48
1.3.47
1.3.46
1.3.45
1.3.44
1.3.43
1.3.42
1.3.41
1.3.40
1.3.39
1.3.38
1.3.37
1.3.36
1.3.35
1.3.34
1.3.33
1.3.32
1.3.31
1.3.30
1.3.29
1.3.28
1.3.26
1.3.25
1.3.24
1.3.23
1.3.22
1.3.21
1.3.20
1.3.19
1.3.18
1.3.17
1.3.16
1.3.15
1.3.14
1.3.12
1.3.11
1.3.10
1.3.9
1.3.8
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.3.0-rc.4
1.3.0-rc.3
1.3.0-rc.2
1.3.0-rc.0
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.1
1.2.0-rc.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
1.0.0-rc.9
1.0.0-rc.8
1.0.0-rc.7
1.0.0-rc.6
1.0.0-rc.5
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
1.0.0-pre.3
1.0.0-pre.2
1.0.0-pre.1
1.0.0-pre.0
0.43.0
0.42.0-rc.7
0.42.0-rc.6
0.42.0-rc.5
0.42.0-rc.4
0.42.0-rc.3
0.42.0-rc.1
0.42.0-rc.0
0.41.7
0.41.6
0.41.5
0.41.4
0.41.3
0.41.2
0.41.1
0.41.0-rc0
retired
0.41.0-rc.9
0.41.0-rc.8
0.41.0-rc.7
0.41.0-rc.6
0.41.0-rc.5
0.41.0-rc.4
0.41.0-rc.3
0.41.0-rc.2
0.41.0-rc.1
0.41.0-rc.0
0.40.11
0.40.10
0.40.9
0.40.8
0.40.7
0.40.6
0.40.5
0.40.4
0.40.3
0.40.2
0.40.1
0.40.0-rc5
0.40.0-rc4
0.40.0-rc3
0.40.0-rc2
0.40.0-rc1
0.39.0-rc0
0.38.11
0.38.10
0.38.9
0.38.8
0.38.7
0.38.6
0.38.5
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.8
0.37.7
0.37.6
0.37.5
0.37.4
0.37.3
0.37.2
0.37.1
0.37.0
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.5
0.35.4
0.35.3
0.35.1
0.34.5
0.34.4
0.34.3
0.34.2
0.34.1
0.34.0
0.33.1
0.33.0
0.32.2
0.32.1
0.32.0
0.31.1
0.30.1
0.29.6
0.29.5
0.29.4
0.29.3
0.29.2
0.29.1
0.28.1
0.28.0
0.27.0
0.26.2
0.26.1
0.26.0
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.0
0.23.2
0.23.1
0.23.0
0.22.1
0.22.0
0.21.0
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.1
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.2
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.1
0.2.0
0.1.4
0.1.3
0.1.2
0.1.1
0.1.1-rc.2
0.1.1-rc.1
0.1.1-rc.0
0.1.0
The PostgreSQL data layer for Ash Framework
Current section
Files
Jump to
Current section
Files
lib/sort.ex
defmodule AshPostgres.Sort do
@moduledoc false
require Ecto.Query
def sort(
query,
sort,
resource,
relationship_path \\ [],
binding \\ 0,
type \\ :window
) do
query = AshPostgres.DataLayer.default_bindings(query, resource)
used_aggregates =
Enum.flat_map(sort, fn
{%Ash.Query.Calculation{} = calculation, _} ->
case Ash.Filter.hydrate_refs(
calculation.module.expression(calculation.opts, calculation.context),
%{
resource: resource,
aggregates: %{},
parent_stack: query.__ash_bindings__[:parent_resources] || [],
calculations: %{},
public?: false
}
) do
{:ok, hydrated} ->
Ash.Filter.used_aggregates(hydrated)
_ ->
[]
end
{key, _} ->
case Ash.Resource.Info.aggregate(resource, key) do
nil ->
[]
aggregate ->
[aggregate]
end
_ ->
[]
end)
calcs =
Enum.flat_map(sort, fn
{%Ash.Query.Calculation{} = calculation, _} ->
{:ok, expression} =
calculation.opts
|> calculation.module.expression(calculation.context)
|> Ash.Filter.hydrate_refs(%{
resource: resource,
parent_stack: query.__ash_bindings__[:parent_resources] || [],
public?: false
})
[{calculation, Ash.Filter.move_to_relationship_path(expression, relationship_path)}]
_ ->
[]
end)
{:ok, query} =
AshPostgres.Join.join_all_relationships(
query,
%Ash.Filter{
resource: resource,
expression: Enum.map(calcs, &elem(&1, 1))
},
left_only?: true
)
case AshPostgres.Aggregate.add_aggregates(query, used_aggregates, resource, false, 0) do
{:error, error} ->
{:error, error}
{:ok, query} ->
sort
|> sanitize_sort()
|> Enum.reduce_while({:ok, [], query}, fn
{order, %Ash.Query.Calculation{} = calc}, {:ok, query_expr, query} ->
type =
if calc.type do
AshPostgres.Types.parameterized_type(calc.type, calc.constraints)
else
nil
end
calc.opts
|> calc.module.expression(calc.context)
|> Ash.Filter.hydrate_refs(%{
resource: resource,
parent_stack: query.__ash_bindings__[:parent_resources] || [],
public?: false
})
|> Ash.Filter.move_to_relationship_path(relationship_path)
|> case do
{:ok, expr} ->
bindings =
if query.__ash_bindings__[:parent_bindings] do
Map.update!(query.__ash_bindings__, :parent_bindings, fn parent ->
Map.put(parent, :parent_is_parent_as?, false)
end)
else
query.__ash_bindings__
end
{expr, acc} =
AshPostgres.Expr.dynamic_expr(
query,
expr,
bindings,
false,
type
)
{:cont,
{:ok, query_expr ++ [{order, expr}],
AshPostgres.DataLayer.merge_expr_accumulator(query, acc)}}
{:error, error} ->
{:halt, {:error, error}}
end
{order, sort}, {:ok, query_expr, query} ->
expr =
case find_aggregate_binding(
query.__ash_bindings__.bindings,
relationship_path,
sort
) do
{:ok, binding} ->
aggregate =
Ash.Resource.Info.aggregate(resource, sort) ||
raise "No such aggregate for query aggregate #{inspect(sort)}"
{:ok, attribute_type} =
if aggregate.field do
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)
attr = Ash.Resource.Info.attribute(related, aggregate.field)
if attr && related do
{:ok, AshPostgres.Types.parameterized_type(attr.type, attr.constraints)}
else
{:ok, nil}
end
else
{:ok, nil}
end
default_value =
aggregate.default || Ash.Query.Aggregate.default_value(aggregate.kind)
if is_nil(default_value) do
Ecto.Query.dynamic(field(as(^binding), ^sort))
else
if attribute_type do
Ecto.Query.dynamic(
coalesce(
field(as(^binding), ^sort),
type(^default_value, ^attribute_type)
)
)
else
Ecto.Query.dynamic(coalesce(field(as(^binding), ^sort), ^default_value))
end
end
:error ->
aggregate = Ash.Resource.Info.aggregate(resource, sort)
{binding, sort} =
if aggregate &&
AshPostgres.Aggregate.optimizable_first_aggregate?(resource, aggregate) do
{AshPostgres.Join.get_binding(
resource,
aggregate.relationship_path,
query,
[
:left,
:inner
]
), aggregate.field}
else
{binding, sort}
end
Ecto.Query.dynamic(field(as(^binding), ^sort))
end
{:cont, {:ok, query_expr ++ [{order, expr}], query}}
end)
|> case do
{:ok, [], query} ->
if type == :return do
{:ok, [], query}
else
{:ok, query}
end
{:ok, sort_exprs, query} ->
case type do
:return ->
{:ok, order_to_fragments(sort_exprs), query}
:window ->
new_query = Ecto.Query.order_by(query, ^sort_exprs)
sort_expr = List.last(new_query.order_bys)
new_query =
new_query
|> Map.update!(:windows, fn windows ->
order_by_expr = %{sort_expr | expr: [order_by: sort_expr.expr]}
Keyword.put(windows, :order, order_by_expr)
end)
|> Map.update!(:__ash_bindings__, &Map.put(&1, :__order__?, true))
{:ok, new_query}
:direct ->
{:ok, query |> Ecto.Query.order_by(^sort_exprs) |> set_sort_applied()}
end
{:error, error} ->
{:error, error}
end
end
end
defp set_sort_applied(query) do
Map.update!(query, :__ash_bindings__, &Map.put(&1, :sort_applied?, true))
end
def find_aggregate_binding(bindings, relationship_path, sort) do
Enum.find_value(
bindings,
:error,
fn
{key, %{type: :aggregate, path: ^relationship_path, aggregates: aggregates}} ->
if Enum.any?(aggregates, &(&1.name == sort)) do
{:ok, key}
end
_ ->
nil
end
)
end
def order_to_fragments([]), do: []
def order_to_fragments([last]) do
[do_order_to_fragments(last, false)]
end
def order_to_fragments([first | rest]) do
[do_order_to_fragments(first, true) | order_to_fragments(rest)]
end
def do_order_to_fragments({order, sort}, comma?) do
case {order, comma?} do
{:asc, false} ->
Ecto.Query.dynamic([row], fragment("? ASC", ^sort))
{:desc, false} ->
Ecto.Query.dynamic([row], fragment("? DESC", ^sort))
{:asc_nulls_last, false} ->
Ecto.Query.dynamic([row], fragment("? ASC NULLS LAST", ^sort))
{:asc_nulls_first, false} ->
Ecto.Query.dynamic([row], fragment("? ASC NULLS FIRST", ^sort))
{:desc_nulls_first, false} ->
Ecto.Query.dynamic([row], fragment("? DESC NULLS FIRST", ^sort))
{:desc_nulls_last, false} ->
Ecto.Query.dynamic([row], fragment("? DESC NULLS LAST", ^sort))
"DESC NULLS LAST"
{:asc, true} ->
Ecto.Query.dynamic([row], fragment("? ASC, ", ^sort))
{:desc, true} ->
Ecto.Query.dynamic([row], fragment("? DESC, ", ^sort))
{:asc_nulls_last, true} ->
Ecto.Query.dynamic([row], fragment("? ASC NULLS LAST, ", ^sort))
{:asc_nulls_first, true} ->
Ecto.Query.dynamic([row], fragment("? ASC NULLS FIRST, ", ^sort))
{:desc_nulls_first, true} ->
Ecto.Query.dynamic([row], fragment("? DESC NULLS FIRST, ", ^sort))
{:desc_nulls_last, true} ->
Ecto.Query.dynamic([row], fragment("? DESC NULLS LAST, ", ^sort))
"DESC NULLS LAST"
end
end
def order_to_postgres_order(dir) do
case dir do
:asc -> nil
:asc_nils_last -> " ASC NULLS LAST"
:asc_nils_first -> " ASC NULLS FIRST"
:desc -> " DESC"
:desc_nils_last -> " DESC NULLS LAST"
:desc_nils_first -> " DESC NULLS FIRST"
end
end
defp sanitize_sort(sort) do
sort
|> List.wrap()
|> Enum.map(fn
{sort, {order, context}} ->
{ash_to_ecto_order(order), {sort, context}}
{sort, order} ->
{ash_to_ecto_order(order), sort}
sort ->
sort
end)
end
defp ash_to_ecto_order(:asc_nils_last), do: :asc_nulls_last
defp ash_to_ecto_order(:asc_nils_first), do: :asc_nulls_first
defp ash_to_ecto_order(:desc_nils_last), do: :desc_nulls_last
defp ash_to_ecto_order(:desc_nils_first), do: :desc_nulls_first
defp ash_to_ecto_order(other), do: other
end