Packages
ash
3.29.3
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.
Current section
Files
Jump to
Current section
Files
lib/ash/info/manifest/generator/operator_resolver.ex
# SPDX-FileCopyrightText: 2025 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Info.Manifest.Generator.OperatorResolver do
@moduledoc """
Resolves which predicate operators and functions in the filter capabilities
catalog apply to a given field type, pre-resolving the right-hand-side type
for each match.
Treats the field as the *first argument* of each operator/function signature.
`:any` and `:same` arg-kind sentinels always match. Concrete builtins match
when the type's `kind` agrees; concrete modules match when the type's
`module` agrees. `c:Ash.Type.operator_overloads/0` is consulted for the
type's module — any operator listed there is considered applicable.
## NewType subtype matching
When the field's type module is an `Ash.Type.NewType`, applicability is
evaluated against both the NewType and its `subtype_of/0` base type. This
mirrors how ash_graphql derives filter inputs for NewType fields — operators
with concrete signatures (e.g. `:contains` requires `:string`) match the
underlying base.
## Ordered comparison trimming
For `:array`- and `:boolean`-kind field types, ordered comparison
operators (`:<`, `:>`, `:<=`, `:>=`) are dropped from the result. They
survive the structural signature match (their sigs are `[:same, :any]`,
which accepts anything) but the orderings aren't useful: no common Ash
data layer offers a meaningful whole-array compare (Postgres'
lexicographic compare is rarely what users want, Ets has no notion at
all), and booleans have no meaningful ordering for filter UX. Equality,
membership, and `is_nil` are preserved.
## RHS resolution
For each applicable entry, the matching signature's *second* arg drives the
`rhs` value on the returned `%ApplicableOperator{}`/`%ApplicableFunction{}`.
Single-arg signatures (e.g. `Eq`'s `[:any]` and `[:same]` shorthand) yield
`rhs: :same`. Multi-arg signatures yield:
* second arg `:same` → `:same`
* second arg `:any` → `:any`
* second arg concrete → `{:concrete, type_ref}`
* second arg `{:array, t}` → `{:array, rhs(t)}`
Operators that apply only via `c:Ash.Type.operator_overloads/0` (no matching
signature found structurally) default to `rhs: :same`.
"""
alias Ash.Info.Manifest.{
ApplicableFunction,
ApplicableOperator,
ArgumentSignature,
FilterCapabilities,
Type
}
alias Ash.Info.Manifest.Generator.TypeResolver
@ordered_comparison_operators MapSet.new([:<, :>, :<=, :>=])
@doc """
Resolve applicable operators and functions for a field type.
Returns `{[ApplicableOperator.t()], [ApplicableFunction.t()]}` — each entry
pairs an operator/function name with its pre-resolved right-hand-side type.
When `resource_data_layer` is given, data-layer-tagged functions
(`%Function{data_layer_module: dl}` with `dl != nil`) are filtered to only
those whose `data_layer_module` matches. Untagged (builtin) functions are
always considered. Pass `nil` to hide all data-layer-tagged functions —
use the default `resolve/2` when there's no data-layer context.
"""
@spec resolve(Type.t(), FilterCapabilities.t(), module() | nil) ::
{[ApplicableOperator.t()], [ApplicableFunction.t()]}
def resolve(%Type{} = field_type, %FilterCapabilities{} = caps, resource_data_layer \\ nil) do
predicate_ops = MapSet.new(caps.predicate_operators)
predicate_fns = MapSet.new(caps.predicate_functions)
overloaded = overloaded_operator_names(field_type)
candidates = candidate_types(field_type)
op_blacklist = blacklisted_operators(field_type)
operators =
caps.operators
|> Enum.filter(fn op ->
op.name in predicate_ops and op.name not in op_blacklist
end)
|> Enum.flat_map(fn op ->
case match_with_rhs(op, candidates) do
{:ok, rhs} ->
[%ApplicableOperator{name: op.name, rhs: rhs}]
:error ->
if op.name in overloaded do
[%ApplicableOperator{name: op.name, rhs: :same}]
else
[]
end
end
end)
|> Enum.uniq_by(& &1.name)
functions =
caps.functions
|> Enum.filter(fn fun ->
fun.name in predicate_fns and data_layer_visible?(fun, resource_data_layer)
end)
|> Enum.flat_map(fn fun ->
case match_with_rhs(fun, candidates) do
{:ok, rhs} -> [%ApplicableFunction{name: fun.name, rhs: rhs}]
:error -> []
end
end)
|> Enum.uniq_by(& &1.name)
{operators, functions}
end
@doc """
Resolve applicable custom expressions for a field type.
Same shape as the operator/function resolution but against
`caps.custom_expressions` filtered by `predicate_custom_expressions`.
Returns a list of `%ApplicableCustomExpression{}`.
"""
@spec resolve_custom_expressions(Type.t(), FilterCapabilities.t()) ::
[Ash.Info.Manifest.ApplicableCustomExpression.t()]
def resolve_custom_expressions(%Type{} = field_type, %FilterCapabilities{} = caps) do
predicate_ces = MapSet.new(caps.predicate_custom_expressions)
candidates = candidate_types(field_type)
caps.custom_expressions
|> Enum.filter(&(&1.name in predicate_ces))
|> Enum.flat_map(fn ce ->
case match_with_rhs(ce, candidates) do
{:ok, rhs} ->
[%Ash.Info.Manifest.ApplicableCustomExpression{name: ce.name, rhs: rhs}]
:error ->
[]
end
end)
|> Enum.uniq_by(& &1.name)
end
defp data_layer_visible?(%{data_layer_module: nil}, _resource_data_layer), do: true
defp data_layer_visible?(%{data_layer_module: dl}, dl), do: true
defp data_layer_visible?(_, _), do: false
# Try each signature against each candidate. First match wins; returns the
# signature so we can derive the RHS from its second arg.
defp match_with_rhs(%{signatures: :var_args}, _candidates), do: :error
defp match_with_rhs(%{signatures: []}, _candidates), do: :error
defp match_with_rhs(%{signatures: signatures}, candidates) when is_list(signatures) do
Enum.find_value(signatures, :error, fn sig ->
if Enum.any?(candidates, &first_arg_matches?(sig, &1)) do
{:ok, rhs_from_signature(sig)}
end
end)
end
defp match_with_rhs(_, _), do: :error
defp rhs_from_signature(%ArgumentSignature{args: [_only]}), do: :same
defp rhs_from_signature(%ArgumentSignature{args: [_first, second | _]}) do
arg_to_rhs(second)
end
defp rhs_from_signature(_), do: :any
defp arg_to_rhs(%{kind: :same}), do: :same
defp arg_to_rhs(%{kind: :any}), do: :any
defp arg_to_rhs(%{kind: :concrete, type_ref: module}) when not is_nil(module),
do: {:concrete, module}
defp arg_to_rhs(%{kind: :array, of: inner}), do: {:array, arg_to_rhs(inner)}
defp arg_to_rhs(_), do: :any
defp first_arg_matches?(%ArgumentSignature{args: [first | _]}, field_type) do
arg_matches_type?(first, field_type)
end
defp first_arg_matches?(_, _), do: false
defp arg_matches_type?(%{kind: :any}, _), do: true
defp arg_matches_type?(%{kind: :same}, _), do: true
defp arg_matches_type?(%{kind: :array, of: inner_spec}, %Type{
kind: :array,
item_type: %Type{} = item_type
}) do
arg_matches_type?(inner_spec, item_type)
end
defp arg_matches_type?(%{kind: :concrete, type_ref: module}, %Type{module: module})
when not is_nil(module),
do: true
defp arg_matches_type?(_, _), do: false
defp blacklisted_operators(%Type{kind: :array}), do: @ordered_comparison_operators
defp blacklisted_operators(%Type{kind: :boolean}), do: @ordered_comparison_operators
defp blacklisted_operators(_), do: MapSet.new()
defp candidate_types(%Type{module: module} = field_type)
when is_atom(module) and not is_nil(module) do
Code.ensure_loaded(module)
if Ash.Type.NewType.new_type?(module) do
base = Ash.Type.NewType.subtype_of(module)
[field_type, TypeResolver.resolve(base)]
else
[field_type]
end
rescue
_ -> [field_type]
end
defp candidate_types(field_type), do: [field_type]
defp overloaded_operator_names(%Type{module: module})
when is_atom(module) and not is_nil(module) do
Code.ensure_loaded(module)
if function_exported?(module, :operator_overloads, 0) do
module.operator_overloads()
|> Map.keys()
|> MapSet.new()
else
MapSet.new()
end
rescue
_ -> MapSet.new()
end
defp overloaded_operator_names(_), do: MapSet.new()
end