Current section

Files

Jump to
ash_sql lib aggregate_query.ex
Raw

lib/aggregate_query.ex

defmodule AshSql.AggregateQuery do
@moduledoc false
import Ecto.Query, only: [from: 2, subquery: 1]
def run_aggregate_query(original_query, aggregates, resource, implementation) do
original_query =
AshSql.Bindings.default_bindings(original_query, resource, implementation)
{can_group, cant_group} =
aggregates
|> Enum.split_with(&AshSql.Aggregate.can_group?(resource, &1, original_query))
|> case do
{[one], cant_group} -> {[], [one | cant_group]}
{can_group, cant_group} -> {can_group, cant_group}
end
{global_filter, can_group} =
AshSql.Aggregate.extract_shared_filters(can_group)
query =
case global_filter do
{:ok, global_filter} ->
AshSql.Filter.filter(original_query, global_filter, resource)
:error ->
{:ok, original_query}
end
case query do
{:error, error} ->
{:error, error}
{:ok, query} ->
query =
if query.distinct || query.limit do
query =
query
|> Ecto.Query.exclude(:select)
|> Ecto.Query.exclude(:order_by)
|> Map.put(:windows, [])
from(row in subquery(query), as: ^query.__ash_bindings__.root_binding, select: %{})
else
query
|> Ecto.Query.exclude(:select)
|> Ecto.Query.exclude(:order_by)
|> Map.put(:windows, [])
|> Ecto.Query.select(%{})
end
query =
Enum.reduce(
can_group,
query,
fn agg, query ->
first_relationship =
Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0))
AshSql.Aggregate.add_subquery_aggregate_select(
query,
agg.relationship_path |> Enum.drop(1),
agg,
resource,
false,
first_relationship
)
end
)
result =
case can_group do
[] ->
%{}
_ ->
repo = AshSql.dynamic_repo(resource, implementation, query)
repo.one(query, AshSql.repo_opts(repo, implementation, nil, nil, resource))
end
{:ok, add_single_aggs(result, resource, original_query, cant_group, implementation)}
end
end
def add_single_aggs(result, resource, query, cant_group, implementation) do
Enum.reduce(cant_group, result, fn
%{kind: :exists} = agg, result ->
{:ok, filtered} =
case agg do
%{query: %{filter: filter}} when not is_nil(filter) ->
AshSql.Filter.filter(query, filter, resource)
_ ->
{:ok, query}
end
filtered =
if filtered.distinct || filtered.limit do
filtered =
filtered
|> Ecto.Query.exclude(:select)
|> Ecto.Query.exclude(:order_by)
|> Map.put(:windows, [])
from(row in subquery(filtered), as: ^query.__ash_bindings__.root_binding, select: %{})
else
filtered
|> Ecto.Query.exclude(:select)
|> Ecto.Query.exclude(:order_by)
|> Map.put(:windows, [])
|> Ecto.Query.select(%{})
end
repo = AshSql.dynamic_repo(resource, implementation, filtered)
Map.put(
result || %{},
agg.name,
repo.exists?(filtered, AshSql.repo_opts(repo, implementation, nil, nil, resource))
)
agg, result ->
{:ok, filtered} =
case agg do
%{query: %{filter: filter}} when not is_nil(filter) ->
AshSql.Filter.filter(query, filter, resource)
_ ->
{:ok, query}
end
filtered =
if filtered.distinct do
in_query = filtered |> Ecto.Query.exclude(:distinct) |> Ecto.Query.exclude(:select)
dynamic =
Enum.reduce(Ash.Resource.Info.primary_key(resource), nil, fn key, dynamic ->
if dynamic do
Ecto.Query.dynamic(
[row],
^dynamic and
field(parent_as(^query.__ash_bindings__.root_binding), ^key) ==
field(row, ^key)
)
else
Ecto.Query.dynamic(
[row],
field(parent_as(^query.__ash_bindings__.root_binding), ^key) ==
field(row, ^key)
)
end
end)
in_query =
from(row in in_query, where: ^dynamic)
from(row in query.from.source,
as: ^query.__ash_bindings__.root_binding,
where: exists(in_query)
)
else
filtered
end
filtered =
if filtered.limit do
filtered =
filtered
|> Ecto.Query.exclude(:select)
|> Ecto.Query.exclude(:order_by)
|> Map.put(:windows, [])
from(row in subquery(filtered), as: ^query.__ash_bindings__.root_binding, select: %{})
else
filtered
|> Ecto.Query.exclude(:select)
|> Ecto.Query.exclude(:order_by)
|> Map.put(:windows, [])
|> Ecto.Query.select(%{})
end
first_relationship =
Ash.Resource.Info.relationship(resource, agg.relationship_path |> Enum.at(0))
query =
AshSql.Aggregate.add_subquery_aggregate_select(
AshSql.Bindings.default_bindings(filtered, resource, implementation),
agg.relationship_path |> Enum.drop(1),
%{agg | query: %{agg.query | filter: nil}},
resource,
true,
first_relationship
)
repo = AshSql.dynamic_repo(resource, implementation, query)
Map.merge(
result || %{},
repo.one(
query,
AshSql.repo_opts(repo, query.__ash_bindings__.sql_behaviour, nil, nil, resource)
)
)
end)
end
end