Packages

ash

3.0.0-rc.45
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
ash lib ash query aggregate.ex
Raw

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,
:load,
:read_action,
:agg_name,
join_filters: %{},
context: %{},
authorize?: true,
include_nil?: false,
uniq?: false,
filterable?: true,
sortable?: true,
sensitive?: false
]
@type t :: %__MODULE__{}
@kinds [:count, :first, :sum, :list, :max, :min, :avg, :exists, :custom]
@type kind :: unquote(Enum.reduce(@kinds, &{:|, [], [&1, &2]}))
alias Ash.Error.Query.{NoReadAction, NoSuchRelationship}
require Ash.Query
@doc false
def kinds, do: @kinds
def new!(resource, name, kind, opts \\ []) do
case new(resource, name, kind, opts) do
{:ok, aggregate} ->
aggregate
{:error, error} ->
raise Ash.Error.to_error_class(error)
end
end
@doc false
def subpaths([]), do: []
def subpaths([first | rest]) do
[[first] | Enum.map(subpaths(rest), &[first | &1])]
end
@schema [
path: [
type: {:list, :atom},
doc: "The relationship path to aggregate over. Only used when adding aggregates to a query."
],
query: [
type: :any,
doc:
"A base query to use for the aggregate, or a keyword list to be passed to `Ash.Query.build/2`"
],
field: [
type: :atom,
doc: "The field to use for the aggregate. Not necessary for all aggregate types."
],
default: [
type: :any,
doc: "A default value to use for the aggregate if it returns `nil`."
],
filterable?: [
type: :boolean,
doc: "Whether or not this aggregate may be used in filters.",
default: true
],
sortable?: [
type: :boolean,
doc: "Whether or not this aggregate may be used in sorts.",
default: true
],
type: [
type: :any,
doc: "A type to use for the aggregate."
],
constraints: [
type: :any,
doc: "Type constraints to use for the aggregate."
],
implementation: [
type: :any,
doc: "The implementation for any custom aggregates."
],
read_action: [
type: :atom,
doc: "The read action to use for the aggregate, defaults to the primary read action."
],
uniq?: [
type: :boolean,
default: false,
doc:
"Whether or not to only consider unique values. Only relevant for `count` and `list` aggregates."
],
include_nil?: [
type: :boolean,
default: false,
doc:
"Whether or not to include `nil` values in the aggregate. Only relevant for `list` and `first` aggregates."
],
join_filters: [
type: {:map, {:wrap_list, :atom}, :any},
default: %{},
doc: """
A map of relationship paths (an atom or list of atoms), to an expression to apply when fetching the aggregate data. See the aggregates guide for more.
"""
],
sensitive?: [
type: :boolean,
doc: "Whether or not references to this aggregate will be considered sensitive",
default: false
],
authorize?: [
type: :boolean,
default: true,
doc: """
Whether or not the aggregate query should authorize based on the target action.
See `d:Ash.Resource.Dsl.aggregates|count` for more information.
"""
]
]
@keys Keyword.keys(@schema)
@doc false
def opt_keys do
@keys
end
@doc """
Create a new aggregate, used with `Query.aggregate` or `Ash.aggregate`
Options:
#{Spark.Options.docs(@schema)}
"""
def new(resource, name, kind, opts \\ []) do
opts =
Enum.reject(opts, fn
{_key, nil} ->
true
_ ->
false
end)
with {:ok, opts} <- Spark.Options.validate(opts, @schema) do
related = Ash.Resource.Info.related(resource, opts[:path] || [])
query =
case opts[:query] || Ash.Query.new(related) do
%Ash.Query{} = query -> query
build_opts -> build_query(related, build_opts)
end
opts[:join_filters]
|> Kernel.||(%{})
|> Enum.reduce_while({:ok, %{}}, fn {path, filter}, {:ok, acc} ->
case parse_join_filter(resource, path, filter) do
{:ok, filter} ->
{:cont, {:ok, Map.put(acc, path, filter)}}
{:error, error} ->
{:halt, {:error, error}}
end
end)
|> case do
{:ok, join_filters} ->
relationship = opts[:path] || []
field = opts[:field]
default = opts[:default]
filterable? = opts[:filterable?]
sortable? = opts[:sortable?]
sensitive? = opts[:sensitive?]
type = opts[:type]
constraints = Keyword.get(opts, :constraints, [])
implementation = opts[:implementation]
uniq? = opts[:uniq?]
read_action = opts[:read_action]
authorize? = opts[:authorize?]
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
related = Ash.Resource.Info.related(resource, relationship)
attribute_type =
if field do
case Ash.Resource.Info.field(related, field) do
%{type: type, constraints: constraints} ->
{:ok, type, constraints}
_ ->
{:error, "No such field for #{inspect(related)}: #{inspect(field)}"}
end
else
{:ok, nil, constraints}
end
default =
if is_function(default) do
default.()
else
default
end
with :ok <- validate_uniq(uniq?, kind),
{:ok, attribute_type, attribute_constraints} <- attribute_type,
:ok <- validate_path(resource, List.wrap(relationship)),
{:ok, type, constraints} <-
get_type(kind, type, attribute_type, attribute_constraints, constraints),
%{valid?: true} = query <- build_query(related, query) do
{:ok,
%__MODULE__{
name: name,
agg_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,
uniq?: uniq?,
query: query,
filterable?: filterable?,
sortable?: sortable?,
sensitive?: sensitive?,
authorize?: authorize?,
read_action: read_action,
join_filters: Map.new(join_filters, fn {key, value} -> {List.wrap(key), value} end)
}}
else
%{valid?: false} = query ->
{:error, query.errors}
{:error, error} ->
{:error, error}
end
{:error, error} ->
{:error, error}
end
end
end
defp parse_join_filter(resource, path, filter) do
[last_relationship | relationships] =
path_to_reversed_relationships(resource, path)
top_parent_resource = (List.last(relationships) || last_relationship).source
parent_resources =
relationships |> Enum.map(& &1.destination) |> Enum.concat([top_parent_resource])
Ash.Filter.parse(last_relationship.destination, filter, %{
parent_stack: parent_resources
})
end
defp path_to_reversed_relationships(resource, path, acc \\ [])
defp path_to_reversed_relationships(_resource, [], acc), do: acc
defp path_to_reversed_relationships(resource, [first | rest], acc) do
relationship = Ash.Resource.Info.relationship(resource, first)
if !relationship do
raise ArgumentError, "No such relationship: #{inspect(resource)}.#{first} in join_filter"
end
path_to_reversed_relationships(relationship.destination, rest, [
relationship | acc
])
end
defp validate_uniq(true, kind) when kind in [:count, :list], do: :ok
defp validate_uniq(true, kind),
do:
{:error,
"#{kind} aggregates do not support the `uniq?` option. Only count and list are supported currently."}
defp validate_uniq(_, _), do: :ok
defp get_type(:custom, type, _, _attribute_constraints, provided_constraints),
do: {:ok, type, provided_constraints || []}
defp get_type(kind, _, attribute_type, attribute_constraints, provided_constraints) do
kind_to_type(kind, attribute_type, attribute_constraints || provided_constraints)
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, relationship: 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
@doc false
def split_aggregate_opts(opts) do
{left, right} = Keyword.split(opts, opt_keys())
right =
case Keyword.fetch(left, :authorize?) do
{:ok, value} ->
Keyword.put(right, :authorize?, value)
:error ->
right
end
case Keyword.fetch(right, :action) do
{:ok, action} ->
{Keyword.put(left, :read_action, action), right}
:error ->
{left, right}
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(:exists), do: nil
def default_value(:list), do: []
def default_value(:custom), do: nil
@doc false
def build_query(resource, nil), do: Ash.Query.new(resource)
def build_query(resource, build_opts) when is_list(build_opts) do
cond do
build_opts[:limit] ->
Ash.Query.add_error(resource, "Cannot set limit on aggregate query")
build_opts[:offset] && build_opts[:offset] != 0 ->
Ash.Query.add_error(resource, "Cannot set offset on aggregate query")
true ->
case Ash.Query.build(resource, build_opts) do
%{valid?: true} = query ->
build_query(resource, query)
%{valid?: false} = query ->
query
end
end
end
def build_query(_resource, %Ash.Query{} = query) do
cond do
query.limit ->
Ash.Query.add_error(query, "Cannot set limit on aggregate query")
query.offset && query.offset != 0 ->
Ash.Query.add_error(query, "Cannot set offset on aggregate query")
true ->
Ash.Query.unset(query, [:load, :limit, :offset])
end
end
@doc false
def kind_to_type({:custom, type}, _attribute_type, _attribute_constraints), do: {:ok, type, []}
def kind_to_type(kind, attribute_type, attribute_constraints)
when kind in [:first, :sum, :max, :min],
do: {:ok, attribute_type, attribute_constraints || []}
def kind_to_type(:list, attribute_type, attribute_constraints),
do: {:ok, {:array, attribute_type}, [items: attribute_constraints || []]}
def kind_to_type(kind, attribute_type, _attribute_constraints) do
with {:ok, type} <- kind_to_type(kind, attribute_type) do
{:ok, type, []}
end
end
defp kind_to_type({:custom, type}, _attribute_type), do: {:ok, type}
defp kind_to_type(:count, _attribute_type), do: {:ok, Ash.Type.Integer}
defp kind_to_type(:exists, _attribute_type), do: {:ok, Ash.Type.Boolean}
defp kind_to_type(kind, nil), do: {:error, "Must provide field type for #{kind}"}
defp kind_to_type(:avg, _attribute_type), do: {:ok, :float}
defp kind_to_type(kind, attribute_type) when kind in [:first, :sum, :max, :min],
do: {:ok, attribute_type}
defp kind_to_type(:list, attribute_type), do: {:ok, {:array, attribute_type}}
defp kind_to_type(kind, _attribute_type), do: {:error, "Invalid aggregate kind: #{kind}"}
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
if is_atom(aggregate.field) do
[to_string(aggregate.field)]
else
case aggregate.field do
%{agg_name: agg_name} ->
[to_string(agg_name)]
%{calc_name: calc_name} ->
[to_string(calc_name)]
_ ->
[inspect(aggregate.field)]
end
end
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