Packages
ash_sql
0.2.10
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.93
0.2.92
0.2.91
0.2.90
0.2.89
0.2.88
0.2.87
0.2.86
0.2.85
0.2.84
0.2.83
0.2.82
0.2.81
0.2.80
0.2.79
0.2.78
0.2.77
0.2.76
0.2.75
0.2.74
0.2.73
0.2.72
0.2.71
0.2.70
0.2.69
0.2.68
0.2.67
0.2.66
0.2.65
0.2.64
0.2.63
0.2.62
0.2.61
0.2.60
0.2.59
0.2.58
0.2.57
0.2.56
0.2.55
0.2.54
0.2.53
0.2.52
0.2.51
0.2.50
0.2.49
0.2.48
0.2.47
0.2.46
0.2.45
0.2.44
0.2.43
0.2.42
0.2.41
0.2.40
0.2.39
0.2.38
0.2.37
0.2.36
0.2.35
0.2.34
0.2.33
0.2.32
0.2.31
0.2.30
0.2.29
0.2.28
0.2.27
0.2.26
0.2.25
0.2.24
0.2.23
0.2.22
0.2.21
0.2.20
0.2.19
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
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.3
0.1.2
0.1.1-rc.20
0.1.1-rc.19
0.1.1-rc.18
0.1.1-rc.17
0.1.1-rc.16
0.1.1-rc.15
0.1.1-rc.14
0.1.1-rc.13
0.1.1-rc.12
0.1.1-rc.11
0.1.1-rc.10
0.1.1-rc.9
0.1.1-rc.8
0.1.1-rc.7
0.1.1-rc.6
0.1.1-rc.5
0.1.1-rc.4
0.1.1-rc.3
0.1.1-rc.2
0.1.1-rc.1
0.1.1-rc.0
Shared utilities for ecto-based sql data layers.
Current section
Files
Jump to
Current section
Files
lib/distinct.ex
defmodule AshSql.Distinct do
@moduledoc false
require Ecto.Query
import Ecto.Query, only: [from: 2]
def distinct(query, empty, resource) when empty in [nil, []] do
query |> AshSql.Sort.apply_sort(query.__ash_bindings__[:sort], resource)
end
def distinct(query, distinct_on, resource) do
case get_distinct_statement(query, distinct_on) do
{:ok, {distinct_statement, query}} ->
%{query | distinct: distinct_statement}
|> AshSql.Sort.apply_sort(query.__ash_bindings__[:sort], resource)
{:error, {distinct_statement, query}} ->
query
|> Ecto.Query.exclude(:order_by)
|> AshSql.Bindings.default_bindings(resource, query.__ash_bindings__.sql_behaviour)
|> Map.put(:distinct, distinct_statement)
|> AshSql.Sort.apply_sort(
query.__ash_bindings__[:distinct_sort] || query.__ash_bindings__[:sort],
resource,
:direct
)
|> case do
{:ok, distinct_query} ->
on =
Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic ->
if dynamic do
Ecto.Query.dynamic(
[row, distinct],
^dynamic and field(row, ^key) == field(distinct, ^key)
)
else
Ecto.Query.dynamic([row, distinct], field(row, ^key) == field(distinct, ^key))
end
end)
joined_query_source =
Enum.reduce(
[
:join,
:order_by,
:group_by,
:having,
:distinct,
:select,
:combinations,
:with_ctes,
:limit,
:offset,
:lock,
:preload,
:update,
:where
],
query,
&Ecto.Query.exclude(&2, &1)
)
joined_query =
from(row in joined_query_source,
join: distinct in subquery(distinct_query),
on: ^on
)
from([row, distinct] in joined_query,
select: distinct
)
|> AshSql.Bindings.default_bindings(resource, query.__ash_bindings__.sql_behaviour)
|> AshSql.Sort.apply_sort(query.__ash_bindings__[:sort], resource)
|> case do
{:ok, joined_query} ->
{:ok,
Map.update!(
joined_query,
:__ash_bindings__,
&Map.put(&1, :__order__?, query.__ash_bindings__[:__order__?] || false)
)}
{:error, error} ->
{:error, error}
end
{:error, error} ->
{:error, error}
end
end
end
defp get_distinct_statement(query, distinct_on) do
has_distinct_sort? = match?(%{__ash_bindings__: %{distinct_sort: _}}, query)
if has_distinct_sort? do
{:error, default_distinct_statement(query, distinct_on)}
else
sort = query.__ash_bindings__[:sort] || []
distinct =
if Code.ensure_loaded?(Ecto.Query.ByExpr) do
query.distinct ||
struct!(Ecto.Query.ByExpr, expr: [], params: [])
else
query.distinct ||
%Ecto.Query.QueryExpr{
expr: [],
params: []
}
end
if sort == [] do
{:ok, default_distinct_statement(query, distinct_on)}
else
distinct_on
|> Enum.reduce_while({sort, [], [], Enum.count(distinct.params), query}, fn
_, {[], _distinct_statement, _, _count, _query} ->
{:halt, :error}
distinct_on, {[order_by | rest_order_by], distinct_statement, params, count, query} ->
case order_by do
{distinct_on, order} = ^distinct_on ->
{distinct_expr, params, count, query} =
distinct_on_expr(query, distinct_on, params, count)
{:cont,
{rest_order_by, [{order, distinct_expr} | distinct_statement], params, count,
query}}
_ ->
{:halt, :error}
end
end)
|> case do
:error ->
{:error, default_distinct_statement(query, distinct_on)}
{_, result, params, _, query} ->
{:ok,
{%{
distinct
| expr: distinct.expr ++ Enum.reverse(result),
params: distinct.params ++ Enum.reverse(params)
}, query}}
end
end
end
end
defp default_distinct_statement(query, distinct_on) do
distinct =
if Code.ensure_loaded?(Ecto.Query.ByExpr) do
query.distinct ||
struct!(Ecto.Query.ByExpr, expr: [], params: [])
else
query.distinct ||
%Ecto.Query.QueryExpr{
expr: [],
params: []
}
end
{expr, params, _, query} =
Enum.reduce(distinct_on, {[], [], Enum.count(distinct.params), query}, fn
{distinct_on_field, order}, {expr, params, count, query} ->
{distinct_expr, params, count, query} =
distinct_on_expr(query, distinct_on_field, params, count)
{[{order, distinct_expr} | expr], params, count, query}
distinct_on_field, {expr, params, count, query} ->
{distinct_expr, params, count, query} =
distinct_on_expr(query, distinct_on_field, params, count)
{[{:asc, distinct_expr} | expr], params, count, query}
end)
{%{
distinct
| expr: distinct.expr ++ Enum.reverse(expr),
params: distinct.params ++ Enum.reverse(params)
}, query}
end
defp distinct_on_expr(query, field, params, count) do
resource = query.__ash_bindings__.resource
ref =
case field do
%Ash.Query.Calculation{} = calc ->
%Ash.Query.Ref{attribute: calc, relationship_path: [], resource: resource}
field ->
%Ash.Query.Ref{
attribute: Ash.Resource.Info.field(resource, field),
relationship_path: [],
resource: resource
}
end
{dynamic, acc} = AshSql.Expr.dynamic_expr(query, ref, query.__ash_bindings__)
result =
Ecto.Query.Builder.Dynamic.partially_expand(
:distinct,
query,
dynamic,
params,
count
)
expr = elem(result, 0)
new_params = elem(result, 1)
new_count = result |> Tuple.to_list() |> List.last()
{expr, new_params, new_count, AshSql.Expr.merge_accumulator(query, acc)}
end
end