View Source Ash.Query (ash v3.4.37)

A data structure for reading data from a resource.

Ash queries are used for read actions and loads, and ultimately map to queries to a resource's data layer.

Queries are run by calling Ash.read.

Examples:

require Ash.Query

MyApp.Post
|> Ash.Query.filter(likes > 10)
|> Ash.Query.sort([:title])
|> Ash.read!()

MyApp.Author
|> Ash.Query.aggregate(:published_post_count, :posts, query: [filter: [published: true]])
|> Ash.Query.sort(published_post_count: :desc)
|> Ash.Query.limit(10)
|> Ash.read!()

MyApp.Author
|> Ash.Query.load([:post_count, :comment_count])
|> Ash.Query.load(posts: [:comments])
|> Ash.read!()

Summary

Functions

Returns a list of attributes, aggregates, relationships, and calculations that are being loaded

Adds an aggregation to the query.

Adds an around_transaction hook to the query.

Adds a before_action hook to the query.

Builds a query from a keyword list.

Removes a result set previously with set_result/2

Return the underlying data layer query for an ash query

Remove an argument from the query

Ensure the the specified attributes are nil in the query results.

Get results distinct on the provided fields.

Set a sort to determine how distinct records are selected.

Ensures that the given attributes are selected.

Determines if the filter statement of a query is equivalent to the provided expression.

Same as equivalent_to/2 but always returns a boolean. :maybe returns false.

fetches the value of an argument provided to the query or :error

Attach a filter statement to the query.

Attach a filter statement to the query labelled as user input.

Creates a query for a given read action and prepares it.

Gets the value of an argument provided to the query

Limit the results returned from the query

Loads relationships, calculations, or aggregates on the resource.

Adds a resource calculation to the query as a custom calculation with the provided name.

Adds a load statement to the result of an attribute or calculation.

Returns true if the field/relationship or path to field/relationship is being loaded.

Lock the query results.

Merges two query's load statements, for the purpose of handling calculation requirements.

Create a new query

Skip the first n records

Sets the pagination options of the query.

Sets a specific context key to a specific value

Ensure that only the specified attributes are present in the results.

Add an argument to the query, which can be used in filter templates on actions

Merge a map of arguments to the arguments list

Merge a map of values into the query context

Set the query's domain, and any loaded query's domain

Set the result of the action. This will prevent running the underlying datalayer behavior

Sort the results based on attributes, aggregates or calculations.

Attach a sort statement to the query labelled as user input.

Determines if the provided expression would return data that is a suprset of the data returned by the filter on the query.

Same as subset_of/2 but always returns a boolean. :maybe returns false.

Determines if the provided expression would return data that is a subset of the data returned by the filter on the query.

Same as superset_of/2 but always returns a boolean. :maybe returns false.

Removes a field from the list of fields to load

Types

@type around_action_fun() :: (t(), around_callback() -> around_result())
@type around_callback() :: (t() -> around_result())
@type around_result() :: {:ok, [Ash.Resource.record()]} | {:error, Ash.Error.t()}
Link to this type

around_transaction_fun()

View Source
@type around_transaction_fun() :: (t() ->
                               {:ok, Ash.Resource.record()} | {:error, any()})
@type t() :: %Ash.Query{
  __validated_for_action__: atom() | nil,
  action: Ash.Resource.Actions.Read.t() | nil,
  action_failed?: boolean(),
  after_action: [
    (t(), [Ash.Resource.record()] ->
       {:ok, [Ash.Resource.record()]}
       | {:ok, [Ash.Resource.record()], [Ash.Notifier.Notification.t()]}
       | {:error, any()})
  ],
  aggregates: %{optional(atom()) => Ash.Filter.t()},
  arguments: %{optional(atom()) => any()},
  around_transaction: term(),
  authorize_results: [
    (t(), [Ash.Resource.record()] ->
       {:ok, [Ash.Resource.record()]} | {:error, any()})
  ],
  before_action: [(t() -> t())],
  calculations: %{optional(atom()) => :wat},
  context: map(),
  distinct: [atom()],
  distinct_sort: term(),
  domain: module() | nil,
  errors: [Ash.Error.t()],
  filter: Ash.Filter.t() | nil,
  invalid_keys: term(),
  limit: nil | non_neg_integer(),
  load: keyword(keyword()),
  load_through: term(),
  lock: term(),
  offset: non_neg_integer(),
  page: keyword() | nil,
  params: %{optional(atom() | binary()) => any()},
  phase: :preparing | :before_action | :after_action | :executing,
  resource: module(),
  select: nil | [atom()],
  sort: [atom() | {atom(), :asc | :desc}],
  sort_input_indices: term(),
  tenant: term(),
  timeout: pos_integer() | nil,
  to_tenant: term(),
  valid?: boolean()
}

Functions

Link to this function

accessing(query, types \\ [:attributes, :relationships, :calculations, :aggregates], only_public? \\ true)

View Source

Returns a list of attributes, aggregates, relationships, and calculations that are being loaded

Provide a list of field types to narrow down the returned results.

Link to this function

add_error(query, keys \\ [], message)

View Source
Link to this function

after_action(query, func)

View Source
@spec after_action(
  t(),
  (t(), [Ash.Resource.record()] ->
     {:ok, [Ash.Resource.record()]}
     | {:ok, [Ash.Resource.record()], [Ash.Notifier.Notification.t()]}
     | {:error, term()})
) :: t()
Link to this function

aggregate(query, name, kind, relationship)

View Source

Adds an aggregation to the query.

Aggregations are made available on the aggregates field of the records returned

The filter option accepts either a filter or a keyword list of options to supply to build a limiting query for that aggregate. See the DSL docs for each aggregate type in the Resource DSL docs for more information.

Options:

  • query: The query over the destination resource to use as a base for aggregation
  • default: The default value to use if the aggregate returns nil
  • filterable?: Whether or not this aggregate may be referenced in filters
  • type: The type of the aggregate
  • constraints: Type constraints for the aggregate's type
  • implementation: An implementation used when the aggregate kind is custom
  • read_action: The read action to use on the destination resource
  • authorize?: Whether or not to authorize access to this aggregate
  • join_filters: A map of relationship paths to filter expressions. See the aggregates guide for more.
Link to this function

aggregate(query, name, kind, relationship, opts)

View Source
Link to this function

apply_to(query, records, opts \\ [])

View Source
@spec apply_to(t(), records :: [Ash.Resource.record()], opts :: Keyword.t()) ::
  {:ok, [Ash.Resource.record()]}
Link to this function

around_transaction(query, func)

View Source
@spec around_transaction(t(), around_transaction_fun()) :: t()

Adds an around_transaction hook to the query.

Your function will get the query, and a callback that must be called with a query (that may be modified). The callback will return {:ok, results} or {:error, error}. You can modify these values, but the return value must be one of those types.

The around_transaction calls happen first, and then (after they each resolve their callbacks) the before_action hooks are called, followed by the after_action hooks being run. Then, the code that appeared after the callbacks were called is then run.

Warning: using this without understanding how it works can cause big problems. You must call the callback function that is provided to your hook, and the return value must contain the same structure that was given to you, i.e {:ok, result_of_action}.

Link to this function

authorize_results(query, func)

View Source
@spec authorize_results(
  t(),
  (t(), [Ash.Resource.record()] ->
     {:ok, [Ash.Resource.record()]}
     | {:ok, [Ash.Resource.record()], [Ash.Notifier.Notification.t()]}
     | {:error, term()})
) :: t()
Link to this function

before_action(query, func, opts \\ [])

View Source
@spec before_action(
  query :: t(),
  fun :: (t() -> t() | {t(), [Ash.Notifier.Notification.t()]}),
  opts :: Keyword.t()
) :: t()

Adds a before_action hook to the query.

Provide the option prepend?: true to place the hook before all other hooks instead of after.

Link to this function

build(resource, domain \\ nil, keyword)

View Source
@spec build(Ash.Resource.t() | t(), Ash.Domain.t() | nil, Keyword.t()) :: t()

Builds a query from a keyword list.

This is used by certain query constructs like aggregates. It can also be used to manipulate a data structure before passing it to an ash query. It allows for building an entire query struct using only a keyword list.

For example:

Ash.Query.build(MyResource, filter: [name: "fred"], sort: [name: :asc], load: [:foo, :bar], offset: 10)

If you want to use the expression style filters, you can use expr/1.

For example:

import Ash.Expr, only: [expr: 1]

Ash.Query.build(Myresource, filter: expr(name == "marge"))

Options

  • :filter (term/0) - A filter keyword, map or expression

  • :filter_input (term/0) - A filter keyword or map, provided as input from an external source

  • :sort (term/0) - A sort list or keyword

  • :sort_input (term/0) - A sort list or keyword, provided as input from an external source

  • :distinct_sort (term/0) - A distinct_sort list or keyword

  • :limit (integer/0) - A limit to apply

  • :offset (integer/0) - An offset to apply

  • :load (term/0) - A load statement to add to the query

  • :select (term/0) - A select statement to add to the query

  • :ensure_selected (term/0) - An ensure_selected statement to add to the query

  • :aggregate (term/0) - A custom aggregate to add to the query. Can be {name, type, relationship} or {name, type, relationship, build_opts}

  • :calculate (term/0) - A custom calculation to add to the query. Can be {name, module_and_opts} or {name, module_and_opts, context}

  • :distinct (list of atom/0) - A distinct clause to add to the query

  • :context (map/0) - A map to merge into the query context

Link to this function

calculate(query, name, type, module_and_opts, arguments \\ %{}, constraints \\ [], extra_context \\ %{}, new_calculation_opts \\ [])

View Source

Adds a calculation to the query.

Calculations are made available on the calculations field of the records returned

The module_and_opts argument accepts either a module or a {module, opts}. For more information on what that module should look like, see Ash.Resource.Calculation.

@spec clear_result(t()) :: t()

Removes a result set previously with set_result/2

Link to this function

data_layer_query(ash_query, opts \\ [])

View Source

Return the underlying data layer query for an ash query

Link to this function

delete_argument(query, argument_or_arguments)

View Source

Remove an argument from the query

Ensure the the specified attributes are nil in the query results.

Link to this function

distinct(query, distincts)

View Source
@spec distinct(t() | Ash.Resource.t(), Ash.Sort.t()) :: t()

Get results distinct on the provided fields.

Takes a list of fields to distinct on. Each call is additive, so to remove the distinct use unset/2.

Examples:

Ash.Query.distinct(query, [:first_name, :last_name])

Ash.Query.distinct(query, :email)
Link to this function

distinct_sort(query, sorts, opts \\ [])

View Source

Set a sort to determine how distinct records are selected.

If none is set, any sort applied to the query will be used.

This is useful if you want to control how the distinct records are selected without affecting (necessarily, it may affect it if there is no sort applied) the overall sort of the query

Link to this function

ensure_selected(query, fields)

View Source

Ensures that the given attributes are selected.

The first call to select/2 will limit the fields to only the provided fields. Use ensure_selected/2 to say "select this field (or these fields) without deselecting anything else".

See select/2 for more.

Link to this macro

equivalent_to(query, expr)

View Source (macro)

Determines if the filter statement of a query is equivalent to the provided expression.

This uses the satisfiability solver that is used when solving for policy authorizations. In complex scenarios, or when using custom database expressions, (like fragments in ash_postgres), this function may return :maybe. Use supserset_of? to always return a boolean.

Link to this macro

equivalent_to?(query, expr)

View Source (macro)

Same as equivalent_to/2 but always returns a boolean. :maybe returns false.

Link to this function

fetch_argument(query, argument)

View Source
@spec fetch_argument(t(), atom()) :: {:ok, term()} | :error

fetches the value of an argument provided to the query or :error

Link to this macro

filter(query, filter)

View Source (macro)

Attach a filter statement to the query.

The filter is applied as an "and" to any filters currently on the query. For more information on writing filters, see: Ash.Filter.

Link to this function

filter_input(query, filter)

View Source

Attach a filter statement to the query labelled as user input.

Filters added as user input (or filters constructed with Ash.Filter.parse_input) will honor any field policies on resources by replacing any references to the field with nil in cases where the actor should not be able to see the given field.

This function does not expect the expression style filter (because an external source could never reasonably provide that). Instead, use the keyword/map style syntax. For example:

expr(name == "fred")

could be any of

  • map syntax: %{"name" => %{"eq" => "fred"}}
  • keyword syntax: [name: [eq: "fred"]]

See Ash.Filter for more.

Link to this function

for_read(query, action_name, args \\ %{}, opts \\ [])

View Source

Creates a query for a given read action and prepares it.

Multitenancy is not validated until an action is called. This allows you to avoid specifying a tenant until just before calling the domain action.

Arguments

Provide a map or keyword list of arguments for the read action

Opts

  • :actor (term/0) - set the actor, which can be used in any Ash.Resource.Changes configured on the action. (in the context argument)

  • :authorize? (boolean/0) - set authorize?, which can be used in any Ash.Resource.Changes configured on the action. (in the context argument)

  • :tracer (one or a list of module that adopts Ash.Tracer) - A tracer to use. Will be carried over to the action. For more information see Ash.Tracer.

  • :tenant (value that implements the Ash.ToTenant protocol) - set the tenant on the query

  • :load (term/0) - A load statement to apply to the query

  • :skip_unknown_inputs - A list of inputs that, if provided, will be ignored if they are not recognized by the action. Use :* to indicate all unknown keys.

Link to this function

get_argument(query, argument)

View Source
@spec get_argument(t(), atom()) :: term()

Gets the value of an argument provided to the query

@spec limit(t() | Ash.Resource.t(), nil | integer()) :: t()

Limit the results returned from the query

Link to this function

load(query, load_statement, opts \\ [])

View Source

Loads relationships, calculations, or aggregates on the resource.

By default, loading attributes has no effects, as all attributes are returned.

# Loading nested relationships
Ash.Query.load(query, [comments: [:author, :ratings]])

# Loading relationships with a query
Ash.Query.load(query, [comments: [author: author_query]])

By passing the strict?: true option, only specified attributes will be loaded when passing a list of fields to fetch on a relationship, which allows for more optimized data-fetching.

The select statement of any queries inside the load statement will not be affected.

Example:

Ash.load(category, [:name, posts: [:title, :published_at]], strict?: true)

Here, the only fields that will be loaded on the posts relationship are title and published_at, in addition to any other fields that are required to be loaded, like the primary and relevant foreign keys. This entails that when using strict?: true and loading nested relationships, you will also always have to specify all the attributes you want to load alongside the nested relationships.

Example:

Ash.load(post, [:title, :published_at, :other_needed_attribute, category: [:name]], strict?: true)

If no fields are specified on a relationship when using strict?: true, all attributes will be loaded by default.

Example:

Ash.load(category, [:name, :posts], strict?: true)
Link to this function

load_calculation_as(query, calc_name, as_name, opts_or_args \\ %{}, opts \\ [])

View Source

Adds a resource calculation to the query as a custom calculation with the provided name.

Example:

Ash.Query.load_calculation_as(query, :calculation, :some_name, args: %{}, load_through: [:foo])
Link to this function

load_through(query, type, name, load)

View Source

Adds a load statement to the result of an attribute or calculation.

Uses Ash.Type.load/5 to request that the type load nested data.

Returns true if the field/relationship or path to field/relationship is being loaded.

It accepts an atom or a list of atoms, which is treated for as a "path", i.e:

Resource |> Ash.Query.load(friends: [enemies: [:score]]) |> Ash.Query.loading?([:friends, :enemies, :score])
iex> true

Resource |> Ash.Query.load(friends: [enemies: [:score]]) |> Ash.Query.loading?([:friends, :score])
iex> false

Resource |> Ash.Query.load(friends: [enemies: [:score]]) |> Ash.Query.loading?(:friends)
iex> true
@spec lock(t() | Ash.Resource.t(), Ash.DataLayer.lock_type()) :: t()

Lock the query results.

This must be run while in a transaction, and is not supported by all data layers.

Link to this function

merge_query_load(left, right, context)

View Source

Merges two query's load statements, for the purpose of handling calculation requirements.

This should only be used if you are writing a custom type that is loadable. See the callback documentation for Ash.Type.merge_load/4 for more.

Link to this function

new(resource, opts \\ [])

View Source
@spec new(Ash.Resource.t() | t(), opts :: Keyword.t()) :: t()

Create a new query

@spec offset(t() | Ash.Resource.t(), nil | integer()) :: t()

Skip the first n records

@spec page(t() | Ash.Resource.t(), Keyword.t()) :: t()

Sets the pagination options of the query.

Pass nil to disable pagination.

Limit/offset pagination

  • :offset (non_neg_integer/0) - The number of records to skip from the beginning of the query

  • :limit (pos_integer/0) - The number of records to include in the page

  • :filter (term/0) - A filter to apply for pagination purposes, that should not be considered in the full count.
    This is used by the liveview paginator to only fetch the records that were already on the page when refreshing data, to avoid pages jittering.

  • :count (boolean/0) - Whether or not to return the page with a full count of all records

Keyset pagination

  • :before (String.t/0) - Get records that appear before the provided keyset (mutually exclusive with after)

  • :after (String.t/0) - Get records that appear after the provided keyset (mutually exclusive with before)

  • :limit (pos_integer/0) - How many records to include in the page

  • :filter (term/0) - See the filter option for offset pagination, this behaves the same.

  • :count (boolean/0) - Whether or not to return the page with a full count of all records

Link to this function

put_context(query, key, value)

View Source
@spec put_context(t() | Ash.Resource.t(), atom(), term()) :: t()

Sets a specific context key to a specific value

See set_context/2 for more information.

Link to this function

select(query, fields, opts \\ [])

View Source

Ensure that only the specified attributes are present in the results.

The first call to select/2 will replace the default behavior of selecting all attributes. Subsequent calls to select/2 will combine the provided fields unless the replace? option is provided with a value of true.

If a field has been deselected, selecting it again will override that (because a single list of fields is tracked for selection)

Primary key attributes are always selected and cannot be deselected.

When attempting to load a relationship (or manage it with Ash.Changeset.manage_relationship/3), if the source field is not selected on the query/provided data an error will be produced. If loading a relationship with a query, an error is produced if the query does not select the destination field of the relationship.

Use ensure_selected/2 if you wish to make sure a field has been selected, without deselecting any other fields.

Link to this function

selecting?(query, field)

View Source
Link to this function

set_argument(query, argument, value)

View Source

Add an argument to the query, which can be used in filter templates on actions

Link to this function

set_arguments(query, map)

View Source

Merge a map of arguments to the arguments list

@spec set_context(t() | Ash.Resource.t(), map() | nil) :: t()

Merge a map of values into the query context

Link to this function

set_domain(query, domain)

View Source

Set the query's domain, and any loaded query's domain

Link to this function

set_result(query, result)

View Source
@spec set_result(t(), term()) :: t()

Set the result of the action. This will prevent running the underlying datalayer behavior

Link to this function

set_tenant(query, tenant)

View Source
@spec set_tenant(t() | Ash.Resource.t(), Ash.ToTenant.t()) :: t()
Link to this function

sort(query, sorts, opts \\ [])

View Source
@spec sort(t() | Ash.Resource.t(), Ash.Sort.t(), opts :: Keyword.t()) :: t()

Sort the results based on attributes, aggregates or calculations.

Calculations are supported if they are defined with expressions, which can be done one of two ways.

  1. with the shorthand calculate :calc, :type, expr(a + b)
  2. By defining expression/2 in a custom calculation module

See the guide on calculations for more.

Takes a list of fields to sort on, or a keyword list/mixed keyword list of fields and sort directions. The default sort direction is :asc.

Examples:

Ash.Query.sort(query, [:foo, :bar])

Ash.Query.sort(query, [:foo, bar: :desc])

Ash.Query.sort(query, [foo: :desc, bar: :asc])

Options

  • prepend? - set to true to put your sort at the front of the list of a sort is already specified
Link to this function

sort_input(query, sorts, opts \\ [])

View Source

Attach a sort statement to the query labelled as user input.

Sorts added as user input (or filters constructed with Ash.Filter.parse_input) will honor any field policies on resources by replacing any references to the field with nil in cases where the actor should not be able to see the given field.

Link to this macro

subset_of(query, expr)

View Source (macro)

Determines if the provided expression would return data that is a suprset of the data returned by the filter on the query.

This uses the satisfiability solver that is used when solving for policy authorizations. In complex scenarios, or when using custom database expressions, (like fragments in ash_postgres), this function may return :maybe. Use subset_of? to always return a boolean.

Link to this macro

subset_of?(query, expr)

View Source (macro)

Same as subset_of/2 but always returns a boolean. :maybe returns false.

Link to this macro

superset_of(query, expr)

View Source (macro)

Determines if the provided expression would return data that is a subset of the data returned by the filter on the query.

This uses the satisfiability solver that is used when solving for policy authorizations. In complex scenarios, or when using custom database expressions, (like fragments in ash_postgres), this function may return :maybe. Use supserset_of? to always return a boolean.

Link to this macro

superset_of?(query, expr)

View Source (macro)

Same as superset_of/2 but always returns a boolean. :maybe returns false.

@spec unload(t(), [atom()]) :: t()

Removes a field from the list of fields to load

@spec unset(Ash.Resource.t() | t(), atom() | [atom()]) :: t()