Packages
ash
2.4.23
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
Current section
Files
lib/ash/query/aggregate.ex
defmodule Ash.Query.Aggregate do
@moduledoc "Represents an aggregated association value"
defstruct [
:name,
:relationship_path,
:default_value,
:resource,
:query,
:field,
:kind,
:type,
:constraints,
:implementation,
:authorization_filter,
:load,
filterable?: true
]
@type t :: %__MODULE__{}
@kinds [:count, :first, :sum, :list, :max, :min, :avg, :sum, :custom]
@type kind :: unquote(Enum.reduce(@kinds, &{:|, [], [&1, &2]}))
alias Ash.Actions.Load
alias Ash.Engine.Request
alias Ash.Error.Query.{NoReadAction, NoSuchRelationship}
require Ash.Query
@doc false
def kinds, do: @kinds
def new(
resource,
name,
kind,
relationship,
query,
field,
default \\ nil,
filterable? \\ true,
type \\ nil,
constraints \\ [],
implementation \\ nil
) do
if kind == :custom && !type do
raise ArgumentError, "Must supply type when building a `custom` aggregate"
end
if kind == :custom && !implementation do
raise ArgumentError, "Must supply implementation when building a `custom` aggregate"
end
attribute_type =
if field do
related = Ash.Resource.Info.related(resource, relationship)
Ash.Resource.Info.attribute(related, field).type
end
with :ok <- validate_path(resource, List.wrap(relationship)),
{:ok, type} <- get_type(kind, type, attribute_type),
{:ok, query} <- validate_query(query) do
{:ok,
%__MODULE__{
name: name,
resource: resource,
constraints: constraints,
default_value: default || default_value(kind),
relationship_path: List.wrap(relationship),
implementation: implementation,
field: field,
kind: kind,
type: type,
query: query,
filterable?: filterable?
}}
end
end
defp get_type(:custom, type, _), do: {:ok, type}
defp get_type(kind, _, attribute_type) do
kind_to_type(kind, attribute_type)
end
defp validate_path(_, []), do: :ok
defp validate_path(resource, [relationship | rest]) do
case Ash.Resource.Info.relationship(resource, relationship) do
nil ->
{:error, NoSuchRelationship.exception(resource: resource, name: relationship)}
%{type: :many_to_many, through: through, destination: destination} ->
cond do
!Ash.Resource.Info.primary_action(through, :read) ->
{:error, NoReadAction.exception(resource: through, when: "aggregating")}
!Ash.Resource.Info.primary_action(destination, :read) ->
{:error, NoReadAction.exception(resource: destination, when: "aggregating")}
!Ash.DataLayer.data_layer(through) == Ash.DataLayer.data_layer(resource) ->
{:error, "Cannot cross data layer boundaries when building an aggregate"}
true ->
validate_path(destination, rest)
end
relationship ->
cond do
!Ash.Resource.Info.primary_action(relationship.destination, :read) ->
NoReadAction.exception(resource: relationship.destination, when: "aggregating")
!Ash.DataLayer.data_layer(relationship.destination) ==
Ash.DataLayer.data_layer(resource) ->
{:error, "Cannot cross data layer boundaries when building an aggregate"}
true ->
validate_path(relationship.destination, rest)
end
end
end
def default_value(:count), do: 0
def default_value(:first), do: nil
def default_value(:sum), do: nil
def default_value(:max), do: nil
def default_value(:min), do: nil
def default_value(:avg), do: nil
def default_value(:list), do: []
def default_value(:custom), do: nil
defp validate_query(nil), do: {:ok, nil}
defp validate_query(query) do
cond do
query.load != [] ->
{:error, "Cannot load in an aggregate"}
not is_nil(query.limit) ->
{:error, "Cannot limit an aggregate (for now)"}
not (is_nil(query.offset) || query.offset == 0) ->
{:error, "Cannot offset an aggregate (for now)"}
true ->
{:ok, query}
end
end
@doc false
def kind_to_type({:custom, type}, _attribute_type), do: {:ok, type}
def kind_to_type(:count, _attribute_type), do: {:ok, Ash.Type.Integer}
def kind_to_type(kind, nil), do: {:error, "Must provide field type for #{kind}"}
def kind_to_type(:avg, _attribute_type), do: {:ok, :float}
def kind_to_type(kind, attribute_type) when kind in [:first, :sum, :max, :min],
do: {:ok, attribute_type}
def kind_to_type(:list, attribute_type), do: {:ok, {:array, attribute_type}}
def kind_to_type(kind, _attribute_type), do: {:error, "Invalid aggregate kind: #{kind}"}
def requests(initial_query, can_be_in_query?, authorizing?, calculations_in_query, request_path) do
initial_query.aggregates
|> Map.values()
|> Enum.map(&{{&1.resource, &1.relationship_path, []}, &1})
|> Enum.concat(aggregates_from_filter(initial_query))
|> Enum.group_by(&elem(&1, 0))
|> Enum.map(fn {key, value} ->
{key, Enum.uniq_by(Enum.map(value, &elem(&1, 1)), & &1.name)}
end)
|> Enum.uniq()
|> Enum.reduce({[], [], []}, fn {{aggregate_resource, relationship_path, ref_path},
aggregates},
{auth_requests, value_requests, aggregates_in_query} ->
related = Ash.Resource.Info.related(aggregate_resource, relationship_path)
relationship =
Ash.Resource.Info.relationship(
aggregate_resource,
List.first(relationship_path)
)
path_for_checking =
relationship_path
|> tl()
|> Enum.reduce({[], relationship.destination}, fn rel, {path, resource} ->
relationship = Ash.Resource.Info.relationship(resource, rel)
{[relationship | path], relationship.destination}
end)
|> elem(0)
|> Enum.reverse()
{in_query?, reverse_relationship} =
case Load.reverse_relationship_path(relationship, path_for_checking) do
:error ->
{ref_path == [] && can_be_in_query?, nil}
{:ok, reverse_relationship} ->
{ref_path == [] && can_be_in_query? &&
any_aggregate_matching_path_used_in_query?(
initial_query,
relationship_path,
calculations_in_query
), reverse_relationship}
end
auth_request =
if authorizing? do
auth_request(
related,
initial_query,
reverse_relationship,
ref_path ++ relationship_path,
request_path
)
else
nil
end
new_auth_requests =
if auth_request do
[auth_request | auth_requests]
else
auth_requests
end
if in_query? do
{new_auth_requests, value_requests, aggregates_in_query ++ aggregates}
else
if ref_path == [] do
request =
value_request(
initial_query,
related,
reverse_relationship,
relationship_path,
aggregates,
auth_request,
aggregate_resource,
request_path
)
{new_auth_requests, [request | value_requests], aggregates_in_query}
else
{new_auth_requests, value_requests, aggregates_in_query}
end
end
end)
end
defp aggregates_from_filter(query) do
aggs =
query.filter
|> Ash.Filter.used_aggregates(:all, true)
|> Enum.reject(&(&1.relationship_path == []))
|> Enum.map(fn ref ->
{{ref.resource, ref.attribute.relationship_path, ref.attribute.relationship_path},
ref.attribute}
end)
calculations =
query.filter
|> Ash.Filter.used_calculations(query.resource)
|> Enum.flat_map(fn calculation ->
expression = calculation.module.expression(calculation.opts, calculation.context)
case Ash.Filter.hydrate_refs(expression, %{
resource: query.resource,
aggregates: query.aggregates,
calculations: query.calculations,
relationship_path: [],
public?: false
}) do
{:ok, expression} ->
Ash.Filter.used_aggregates(expression)
_ ->
[]
end
end)
|> Enum.map(fn aggregate ->
{{query.resource, aggregate.relationship_path, []}, aggregate}
end)
Enum.uniq_by(aggs ++ calculations, &elem(&1, 1).name)
end
defp auth_request(related, initial_query, reverse_relationship, relationship_path, request_path) do
Request.new(
resource: related,
api: initial_query.api,
async?: false,
query: aggregate_query(related, reverse_relationship, request_path),
path: request_path ++ [:aggregate, relationship_path],
strict_check_only?: true,
action: Ash.Resource.Info.primary_action(related, :read),
name: "authorize aggregate: #{Enum.join(relationship_path, ".")}",
data: []
)
end
defp value_request(
initial_query,
related,
reverse_relationship,
relationship_path,
aggregates,
auth_request,
aggregate_resource,
request_path
) do
pkey = Ash.Resource.Info.primary_key(aggregate_resource)
deps =
if auth_request do
[auth_request.path ++ [:authorization_filter], request_path ++ [:fetch, :data]]
else
[request_path ++ [:fetch, :data]]
end
Request.new(
resource: aggregate_resource,
api: initial_query.api,
query: aggregate_query(related, reverse_relationship, request_path),
path: request_path ++ [:aggregate_values, relationship_path],
action: Ash.Resource.Info.primary_action(aggregate_resource, :read),
name: "fetch aggregate: #{Enum.join(relationship_path, ".")}",
data:
Request.resolve(
deps,
fn data ->
records = get_in(data, request_path ++ [:fetch, :data, :results])
if records == [] do
{:ok, %{}}
else
initial_query =
initial_query
|> Ash.Query.unset([:filter, :sort, :aggregates, :limit, :offset, :select])
|> Ash.Query.select([])
query =
case records do
[record] ->
filter = record |> Map.take(pkey) |> Enum.to_list()
Ash.Query.filter(
initial_query,
^filter
)
records ->
filter = [or: Enum.map(records, &Map.take(&1, pkey))]
Ash.Query.filter(
initial_query,
^filter
)
end
aggregates =
if auth_request do
case get_in(data, auth_request.path ++ [:authorization_filter]) do
nil ->
aggregates
filter ->
Enum.map(aggregates, fn aggregate ->
%{
aggregate
| query: Ash.Query.filter(aggregate.query, ^filter)
}
end)
end
else
aggregates
end
with {:ok, data_layer_query} <- Ash.Query.data_layer_query(query),
{:ok, data_layer_query} <-
add_data_layer_aggregates(
data_layer_query,
aggregates,
initial_query.resource
),
{:ok, results} <-
Ash.DataLayer.run_query(
data_layer_query,
query.resource
) do
loaded_aggregates =
aggregates
|> Enum.map(& &1.load)
|> Enum.reject(&is_nil/1)
all_aggregates = Enum.map(aggregates, & &1.name)
aggregate_values =
Enum.reduce(results, %{}, fn result, acc ->
loaded_aggregate_values = Map.take(result, loaded_aggregates)
all_aggregate_values =
result.aggregates
|> Kernel.||(%{})
|> Map.take(all_aggregates)
|> Map.merge(loaded_aggregate_values)
Map.put(
acc,
Map.take(result, pkey),
all_aggregate_values
)
end)
{:ok, aggregate_values}
else
{:error, error} ->
{:error, error}
end
end
end
)
)
end
defp add_data_layer_aggregates(data_layer_query, aggregates, aggregate_resource) do
Ash.DataLayer.add_aggregates(data_layer_query, aggregates, aggregate_resource)
end
defp aggregate_query(resource, reverse_relationship, request_path) do
Request.resolve(
[request_path ++ [:fetch, :query]],
fn data ->
data_query = get_in(data, request_path ++ [:fetch, :query])
if reverse_relationship do
filter =
Ash.Filter.put_at_path(
data_query.filter,
reverse_relationship
)
{:ok, Ash.Query.filter(resource, ^filter)}
else
{:ok, data_query}
end
end
)
end
defp any_aggregate_matching_path_used_in_query?(query, relationship_path, calculations_in_query) do
filter_aggregates =
if query.filter do
Ash.Filter.used_aggregates(query.filter)
else
[]
end
used_calculations =
Ash.Filter.used_calculations(
query.filter,
query.resource
) ++ calculations_in_query
calculation_aggregates =
used_calculations
|> Enum.filter(&:erlang.function_exported(&1.module, :expression, 2))
|> Enum.flat_map(fn calculation ->
case Ash.Filter.hydrate_refs(
calculation.module.expression(calculation.opts, calculation.context),
%{
resource: query.resource,
aggregates: query.aggregates,
calculations: query.calculations,
relationship_path: [],
public?: false
}
) do
{:ok, hydrated} ->
Ash.Filter.used_aggregates(hydrated)
_ ->
[]
end
end)
if Enum.any?(
filter_aggregates ++ calculation_aggregates,
&(&1.relationship_path == relationship_path)
) do
true
else
sort_aggregates =
Enum.flat_map(query.sort, fn {field, _} ->
case Map.fetch(query.aggregates, field) do
:error ->
[]
{:ok, agg} ->
[agg]
end
end)
sort_calculations =
Enum.flat_map(query.sort, fn
{%Ash.Query.Calculation{} = calc, _} ->
[calc]
{field, _} ->
case Map.fetch(query.calculations, field) do
:error ->
[]
{:ok, calc} ->
[calc]
end
end)
sort_calc_aggregates =
sort_calculations
|> Enum.filter(&:erlang.function_exported(&1.module, :expression, 2))
|> Enum.flat_map(fn calculation ->
case Ash.Filter.hydrate_refs(
calculation.module.expression(calculation.opts, calculation.context),
%{
resource: query.resource,
aggregates: query.aggregates,
calculations: query.calculations,
public?: false
}
) do
{:ok, hydrated} ->
Ash.Filter.used_aggregates(hydrated)
_ ->
[]
end
end)
Enum.any?(
sort_aggregates ++ sort_calc_aggregates,
&(&1.relationship_path == relationship_path)
)
end
end
defimpl Inspect do
import Inspect.Algebra
def inspect(%{query: nil} = aggregate, opts) do
container_doc(
"#" <> to_string(aggregate.kind) <> "<",
[Enum.join(aggregate.relationship_path, ".")],
">",
opts,
fn str, _ -> str end,
separator: ""
)
end
def inspect(%{query: query} = aggregate, opts) do
field =
if aggregate.field do
[aggregate.field]
else
[]
end
container_doc(
"#" <> to_string(aggregate.kind) <> "<",
[
concat([
Enum.join(aggregate.relationship_path ++ field, "."),
concat(" from ", to_doc(query, opts))
])
],
">",
opts,
fn str, _ -> str end
)
end
end
end