Packages
ash
1.1.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.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/query.ex
defmodule Ash.Query do
@moduledoc """
Utilties around constructing/manipulating ash queries.
Ash queries are used for read actions and side loads, and ultimately
map to queries to a resource's data layer.
"""
defstruct [
:api,
:resource,
:filter,
:data_layer_query,
side_load: [],
sort: [],
limit: nil,
offset: 0,
errors: [],
valid?: true
]
@type t :: %__MODULE__{}
defimpl Inspect do
import Inspect.Algebra
def inspect(query, opts) do
opts = %{
opts
| syntax_colors: [
atom: :yellow,
binary: :green,
boolean: :magenta,
list: :cyan,
map: :magenta,
number: :red,
regex: :violet,
tuple: :white
]
}
error_doc =
if Enum.empty?(query.errors) do
empty()
else
concat("errors: ", to_doc(query.errors, opts))
end
container_doc(
"#Ash.Query<",
[
concat("resource: ", inspect(query.resource)),
concat("filter: ", to_doc(query.filter, opts)),
concat("sort: ", to_doc(query.sort, opts)),
concat("limit: ", to_doc(query.limit, opts)),
concat("offset: ", to_doc(query.offset, opts)),
concat("side_load: ", to_doc(query.side_load, opts)),
error_doc
],
">",
opts,
fn str, _ -> str end
)
end
end
alias Ash.Actions.Sort
alias Ash.Error.{InvalidLimit, InvalidOffset}
alias Ash.Error.SideLoad.{InvalidQuery, NoSuchRelationship}
@doc "Create a new query."
def new(resource, api \\ nil) when is_atom(resource) do
%__MODULE__{
api: api,
filter: nil,
resource: resource
}
|> set_data_layer_query()
end
@doc "Set the query's api, and any side loaded query's api"
def set_api(query, api) do
query = to_query(query)
%{query | api: api, side_load: set_side_load_api(query.side_load, api)}
end
@doc "Limit the results returned from the query"
def limit(query, nil), do: query
def limit(query, limit) when is_integer(limit) do
query
|> to_query()
|> Map.put(:limit, max(0, limit))
|> set_data_layer_query()
end
def limit(query, limit) do
add_error(query, :offset, InvalidLimit.exception(limit: limit))
end
@doc "Skip the first n records"
def offset(query, nil), do: query
def offset(query, offset) when is_integer(offset) do
query
|> to_query()
|> Map.put(:offset, max(0, offset))
|> set_data_layer_query()
end
def offset(query, offset) do
query
|> to_query()
|> add_error(:offset, InvalidOffset.exception(offset: offset))
end
@doc "Side loads related entities"
def side_load(query, statement) do
query = to_query(query)
with sanitized_statement <- List.wrap(sanitize_side_loads(statement)),
:ok <- validate_side_load(query.resource, sanitized_statement),
new_side_loads <- merge_side_load(query.side_load, sanitized_statement) do
%{query | side_load: new_side_loads}
else
{:error, errors} ->
Enum.reduce(errors, query, &add_error(&2, :side_load, &1))
end
end
def validate_side_load(resource, side_loads, path \\ []) do
case do_validate_side_load(resource, side_loads, path) do
[] -> :ok
errors -> {:error, errors}
end
end
def do_validate_side_load(_resource, %Ash.Query{} = query, path) do
if query.limit || (query.offset && query.offset != 0) do
[{:error, InvalidQuery.exception(query: query, side_load_path: Enum.reverse(path))}]
else
case query.errors do
[] ->
[]
_errors ->
[
{:error,
InvalidQuery.exception(
query: query,
side_load_path: Enum.reverse(path)
)}
]
end
end
end
def do_validate_side_load(resource, {atom, _} = tuple, path) when is_atom(atom) do
do_validate_side_load(resource, [tuple], path)
end
def do_validate_side_load(resource, side_loads, path) when is_list(side_loads) do
side_loads
|> List.wrap()
|> Enum.flat_map(fn
{_key, %Ash.Query{}} ->
[]
{key, value} ->
case Ash.relationship(resource, key) do
nil ->
[
{:error,
NoSuchRelationship.exception(
resource: resource,
relationship: key,
side_load_path: Enum.reverse(path)
)}
]
relationship ->
validate_matching_query_and_continue(value, resource, key, path, relationship)
end
end)
end
def filter(query, nil), do: query
def filter(query, %Ash.Filter{} = filter) do
query = to_query(query)
new_filter =
case query.filter do
nil ->
{:ok, filter}
existing_filter ->
Ash.Filter.add_to_filter(existing_filter, filter)
end
case new_filter do
{:ok, filter} ->
set_data_layer_query(%{query | filter: filter})
{:error, error} ->
add_error(query, :filter, error)
end
end
def filter(query, statement) do
query = to_query(query)
filter =
if query.filter do
Ash.Filter.add_to_filter(query.filter, statement)
else
Ash.Filter.parse(query.resource, statement)
end
case filter do
{:ok, filter} ->
query
|> Map.put(:filter, filter)
|> set_data_layer_query()
{:error, error} ->
add_error(query, :filter, error)
end
end
def sort(query, sorts) when is_list(sorts) do
query = to_query(query)
sorts
|> Enum.reduce(query, fn
{sort, direction}, query ->
%{query | sort: query.sort ++ [{sort, direction}]}
sort, query ->
%{query | sort: query.sort ++ [{sort, :asc}]}
end)
|> validate_sort()
|> set_data_layer_query()
end
def unset(query, keys) when is_list(keys) do
Enum.reduce(keys, query, &unset(&2, &1))
end
def unset(query, key) when key in [:api, :resource] do
add_error(query, key, "Cannot be unset")
end
def unset(query, key) do
query
|> to_query()
|> struct([{key, Map.get(%__MODULE__{}, key)}])
end
@doc false
def data_layer_query(%{resource: resource} = ash_query, opts \\ []) do
query = Ash.DataLayer.resource_to_query(resource)
with {:ok, query} <- Ash.DataLayer.sort(query, ash_query.sort, resource),
{:ok, query} <- maybe_filter(query, ash_query, opts) do
{:ok, query}
else
{:error, error} -> {:error, error}
end
end
defp validate_sort(%{resource: resource, sort: sort} = query) do
case Sort.process(resource, sort) do
{:ok, new_sort} -> %{query | sort: new_sort}
{:error, error} -> add_error(query, :sort, error)
end
end
defp add_error(query, key, message) do
query = to_query(query)
message =
if is_binary(message) do
"#{key}: #{message}"
else
message
end
%{
query
| errors: [Map.put(Ash.Error.to_ash_error(message), :path, key) | query.errors],
valid?: false
}
end
defp set_data_layer_query(query) do
case data_layer_query(query) do
{:ok, data_layer_query} -> %{query | data_layer_query: data_layer_query}
{:error, error} -> add_error(query, :data_layer_query, error)
end
end
defp validate_matching_query_and_continue(value, resource, key, path, relationship) do
%{destination: relationship_resource} = relationship
case value do
%__MODULE__{resource: query_resource} = destination_query
when query_resource != relationship_resource ->
[
InvalidQuery.exception(
resource: resource,
relationship: key,
query: destination_query,
side_load_path: Enum.reverse(path)
)
]
other ->
do_validate_side_load(relationship.destination, other, [key | path])
end
end
defp maybe_filter(query, %{filter: nil}, _) do
{:ok, query}
end
defp maybe_filter(query, ash_query, opts) do
case Ash.DataLayer.filter(query, ash_query.filter, ash_query.resource) do
{:ok, filtered} ->
if Keyword.get(opts, :only_validate_filter?, true) do
{:ok, query}
else
{:ok, filtered}
end
{:error, error} ->
{:error, error}
end
end
defp set_side_load_api(nil, _), do: nil
defp set_side_load_api([], _), do: []
defp set_side_load_api(%__MODULE__{} = query, api) do
set_api(query, api)
end
defp set_side_load_api(side_loads, api) do
Enum.map(side_loads, fn {key, further} ->
{key, set_side_load_api(further, api)}
end)
end
defp to_query(%__MODULE__{} = query), do: query
defp to_query(resource), do: new(resource)
defp merge_side_load([], right), do: sanitize_side_loads(right)
defp merge_side_load(left, []), do: sanitize_side_loads(left)
defp merge_side_load(
%__MODULE__{side_load: left_side_loads},
%__MODULE__{side_load: right_side_loads} = query
) do
%{query | side_load: merge_side_load(left_side_loads, right_side_loads)}
end
defp merge_side_load(%__MODULE__{} = query, right) when is_list(right) do
Ash.Query.side_load(query, right)
end
defp merge_side_load(left, %Ash.Query{} = query) when is_list(left) do
Ash.Query.side_load(query, left)
end
defp merge_side_load(left, right) when is_atom(left), do: merge_side_load([{left, []}], right)
defp merge_side_load(left, right) when is_atom(right), do: merge_side_load(left, [{right, []}])
defp merge_side_load(left, right) when is_list(left) and is_list(right) do
right
|> sanitize_side_loads()
|> Enum.reduce(sanitize_side_loads(left), fn {rel, rest}, acc ->
Keyword.update(acc, rel, rest, &merge_side_load(&1, rest))
end)
end
defp sanitize_side_loads(side_load) when is_atom(side_load), do: {side_load, []}
defp sanitize_side_loads(%Ash.Query{} = query) do
Map.update!(query, :side_load, &sanitize_side_loads/1)
end
defp sanitize_side_loads(side_loads) do
side_loads
|> List.wrap()
|> Enum.map(fn
{key, value} ->
{key, sanitize_side_loads(value)}
side_load_part ->
cond do
is_atom(side_load_part) -> {side_load_part, []}
is_list(side_load_part) -> sanitize_side_loads(side_load_part)
true -> side_load_part
end
end)
end
end