Packages
ash_sql
0.3.2
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)
)
{calculations_require_rewrite, aggregates_require_rewrite, distinct_query} =
AshSql.Query.rewrite_nested_selects(distinct_query)
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__,
fn ash_bindings ->
ash_bindings
|> Map.put(:__order__?, query.__ash_bindings__[:__order__?] || false)
|> Map.update(
:calculations_require_rewrite,
calculations_require_rewrite,
fn current_calculations_require_rewrite ->
Map.merge(
current_calculations_require_rewrite,
calculations_require_rewrite
)
end
)
|> Map.update(
:aggregates_require_rewrite,
aggregates_require_rewrite,
fn current_aggregates_require_rewrite ->
Map.merge(
current_aggregates_require_rewrite,
aggregates_require_rewrite
)
end
)
end
)}
{: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