View Source Ash.DataLayer behaviour (ash v2.21.12)

The interface for being an ash data layer.

This is a large behaviour, and this capability is not complete, but the idea is to have a large amount of optional callbacks, and use the can?/2 callback to ensure that the engine only ever tries to interact with the data layer in ways that it supports.

Summary

Functions

The data layer of the resource, or nil if it does not have one

Whether or not the data layer supports a specific feature

Custom functions supported by the data layer of the resource

Whether or not lateral joins should be used for many to many relationships by default

Rolls back the current transaction

Wraps the execution of the function in a transaction with the resource's data_layer

Types

@type bulk_create_options() :: %{
  batch_size: pos_integer(),
  return_records?: boolean(),
  upsert?: boolean(),
  upsert_keys: nil | [atom()],
  upsert_fields:
    nil
    | [atom()]
    | :replace_all
    | {:replace, [atom()]}
    | {:replace_all_except, [atom()]},
  tenant: term()
}
@type bulk_update_options() :: %{return_records?: boolean(), tenant: term()}
@type data_layer_query() :: struct()
@type feature() ::
  :transact
  | :multitenancy
  | {:atomic, :update}
  | {:atomic, :upsert}
  | {:lateral_join, [Ash.Resource.t()]}
  | {:join, Ash.Resource.t()}
  | {:aggregate, Ash.Query.Aggregate.kind()}
  | {:aggregate_relationship, Ash.Resource.Relationships.relationship()}
  | {:query_aggregate, Ash.Query.Aggregate.kind()}
  | :select
  | :expr_error
  | :expression_calculation_sort
  | :aggregate_filter
  | :aggregate_sort
  | :boolean_filter
  | :async_engine
  | :bulk_create
  | :update_query
  | :destroy_query
  | :create
  | :read
  | :update
  | :destroy
  | :limit
  | :offset
  | :transact
  | :filter
  | :composite_type
  | {:lock, lock_type()}
  | {:filter_expr, struct()}
  | {:filter_relationship, Ash.Resource.Relationships.relationship()}
  | :sort
  | {:sort, Ash.Type.t()}
  | :upsert
  | :composite_primary_key
@type lateral_join_link() ::
  {Ash.Resource.t(), atom(), atom(), Ash.Resource.Relationships.relationship()}
@type lock_type() :: :for_update | term()
@type t() :: module()
@type transaction_reason() ::
  %{
    :type => :create,
    :metadata => %{resource: Ash.Resource.t(), action: atom()},
    optional(:data_layer_context) => %{}
  }
  | %{
      :type => :update,
      :metadata => %{
        resource: Ash.Resource.t(),
        action: atom(),
        record: Ash.Resource.record(),
        actor: term()
      },
      optional(:data_layer_context) => %{}
    }
  | %{
      :type => :destroy,
      :metadata => %{
        resource: Ash.Resource.t(),
        action: atom(),
        record: Ash.Resource.record(),
        actor: term()
      },
      optional(:data_layer_context) => %{}
    }
  | %{
      :type => :read,
      :metadata => %{
        resource: Ash.Resource.t(),
        query: Ash.Query.t(),
        actor: term()
      },
      optional(:data_layer_context) => %{}
    }
  | %{
      :type => :flow_transaction,
      :metadata => %{
        resource: Ash.Resource.t(),
        input: Ash.ActionInput.t(),
        action: atom(),
        actor: term()
      },
      optional(:data_layer_context) => %{}
    }
  | %{
      :type => :generic,
      :metadata => %{
        step_name: atom() | [term()],
        flow: module(),
        actor: term()
      },
      optional(:data_layer_context) => %{}
    }
  | %{type: :custom, metadata: map()}
  | %{type: atom(), metadata: map()}

Callbacks

Link to this callback

add_aggregate(data_layer_query, t, t)

View Source (optional)
@callback add_aggregate(
  data_layer_query(),
  Ash.Query.Aggregate.t(),
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
Link to this callback

add_aggregates(data_layer_query, list, t)

View Source (optional)
@callback add_aggregates(
  data_layer_query(),
  [Ash.Query.Aggregate.t()],
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
Link to this callback

add_calculation(data_layer_query, t, expression, t)

View Source (optional)
@callback add_calculation(
  data_layer_query(),
  Ash.Query.Calculation.t(),
  expression :: any(),
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
Link to this callback

add_calculations(data_layer_query, list, t)

View Source (optional)
@callback add_calculations(
  data_layer_query(),
  [{Ash.Query.Calculation.t(), expression :: any()}],
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
Link to this callback

bulk_create(t, t, options)

View Source (optional)
@callback bulk_create(
  Ash.Resource.t(),
  Enumerable.t(Ash.Changeset.t()),
  options :: bulk_create_options()
) ::
  :ok
  | {:ok, Enumerable.t(Ash.Resource.record())}
  | {:error, Ash.Error.t()}
  | {:error, :no_rollback, Ash.Error.t()}
@callback can?(Ash.Resource.t() | Spark.Dsl.t(), feature()) :: boolean()
@callback create(Ash.Resource.t(), Ash.Changeset.t()) ::
  {:ok, Ash.Resource.record()}
  | {:error, term()}
  | {:error, :no_rollback, term()}
Link to this callback

destroy(t, t)

View Source (optional)
@callback destroy(Ash.Resource.t(), Ash.Changeset.t()) :: :ok | {:error, term()}
Link to this callback

destroy_query(data_layer_query, t, t, opts)

View Source (optional)
@callback destroy_query(
  data_layer_query(),
  Ash.Changeset.t(),
  Ash.Resource.t(),
  opts :: bulk_update_options()
) ::
  :ok
  | {:ok, Enumerable.t(Ash.Resource.record())}
  | {:error, Ash.Error.t()}
  | {:error, :no_rollback, Ash.Error.t()}
Link to this callback

distinct(data_layer_query, list, resource)

View Source (optional)
@callback distinct(data_layer_query(), [atom()], resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this callback

distinct_sort(data_layer_query, t, resource)

View Source (optional)
@callback distinct_sort(data_layer_query(), Ash.Sort.t(), resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this callback

filter(data_layer_query, t, resource)

View Source (optional)
@callback filter(data_layer_query(), Ash.Filter.t(), resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
@callback functions(Ash.Resource.t()) :: [module()]
Link to this callback

in_transaction?(t)

View Source (optional)
@callback in_transaction?(Ash.Resource.t()) :: boolean()
Link to this callback

limit(data_layer_query, limit, resource)

View Source (optional)
@callback limit(
  data_layer_query(),
  limit :: non_neg_integer(),
  resource :: Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
Link to this callback

lock(data_layer_query, lock_type, resource)

View Source (optional)
@callback lock(data_layer_query(), lock_type(), resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this callback

offset(data_layer_query, offset, resource)

View Source (optional)
@callback offset(
  data_layer_query(),
  offset :: non_neg_integer(),
  resource :: Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
Link to this callback

prefer_lateral_join_for_many_to_many?()

View Source (optional)
@callback prefer_lateral_join_for_many_to_many?() :: boolean()
@callback resource_to_query(Ash.Resource.t(), Ash.Api.t()) :: data_layer_query()
Link to this callback

return_query(data_layer_query, t)

View Source (optional)
@callback return_query(data_layer_query(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this callback

rollback(t, term)

View Source (optional)
@callback rollback(Ash.Resource.t(), term()) :: no_return()
Link to this callback

run_aggregate_query(data_layer_query, list, t)

View Source (optional)
@callback run_aggregate_query(
  data_layer_query(),
  [Ash.Query.Aggregate.t()],
  Ash.Resource.t()
) :: {:ok, map()} | {:error, term()}
Link to this callback

run_aggregate_query_with_lateral_join(data_layer_query, list, list, destination_resource, list)

View Source (optional)
@callback run_aggregate_query_with_lateral_join(
  data_layer_query(),
  [Ash.Query.Aggregate.t()],
  [Ash.Resource.record()],
  destination_resource :: Ash.Resource.t(),
  [lateral_join_link()]
) :: {:ok, [Ash.Resource.t()]} | {:error, term()}
Link to this callback

run_query(data_layer_query, t)

View Source (optional)
@callback run_query(data_layer_query(), Ash.Resource.t()) ::
  {:ok, [Ash.Resource.record()]}
  | {:error, term()}
  | {:error, :no_rollback, term()}
Link to this callback

run_query_with_lateral_join(data_layer_query, list, source_resource, list)

View Source (optional)
@callback run_query_with_lateral_join(
  data_layer_query(),
  [Ash.Resource.record()],
  source_resource :: Ash.Resource.t(),
  [lateral_join_link()]
) :: {:ok, [Ash.Resource.record()]} | {:error, term()}
Link to this callback

select(data_layer_query, select, resource)

View Source (optional)
@callback select(
  data_layer_query(),
  select :: [atom()],
  resource :: Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
Link to this callback

set_context(t, data_layer_query, map)

View Source (optional)
@callback set_context(Ash.Resource.t(), data_layer_query(), map()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this callback

set_tenant(t, data_layer_query, term)

View Source (optional)
@callback set_tenant(Ash.Resource.t(), data_layer_query(), term()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this callback

sort(data_layer_query, t, resource)

View Source (optional)
@callback sort(data_layer_query(), Ash.Sort.t(), resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
@callback source(Ash.Resource.t()) :: String.t()
Link to this callback

transaction(t, function, arg3, reason)

View Source (optional)
@callback transaction(
  Ash.Resource.t(),
  (-> term()),
  nil | pos_integer(),
  reason :: transaction_reason()
) :: {:ok, term()} | {:error, term()}
Link to this callback

transform_query(t)

View Source (optional)
@callback transform_query(Ash.Query.t()) :: Ash.Query.t()
@callback update(Ash.Resource.t(), Ash.Changeset.t()) ::
  {:ok, Ash.Resource.record()}
  | {:error, term()}
  | {:error, :no_rollback, term()}
Link to this callback

update_query(data_layer_query, t, t, opts)

View Source (optional)
@callback update_query(
  data_layer_query(),
  Ash.Changeset.t(),
  Ash.Resource.t(),
  opts :: bulk_update_options()
) ::
  :ok
  | {:ok, Enumerable.t(Ash.Resource.record())}
  | {:error, Ash.Error.t()}
  | {:error, :no_rollback, Ash.Error.t()}
Link to this callback

upsert(t, t, list)

View Source (optional)
@callback upsert(Ash.Resource.t(), Ash.Changeset.t(), [atom()]) ::
  {:ok, Ash.Resource.record()}
  | {:error, term()}
  | {:error, :no_rollback, term()}

Functions

Link to this function

add_aggregates(query, aggregates, resource)

View Source
@spec add_aggregates(data_layer_query(), [Ash.Query.Aggregate.t()], Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

add_calculations(query, calculations, resource)

View Source
@spec add_calculations(
  data_layer_query(),
  [{Ash.Query.Calculation.t(), expression :: term()}],
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
Link to this function

bulk_create(resource, changesets, options)

View Source
@spec bulk_create(
  Ash.Resource.t(),
  Enumerable.t(Ash.Changeset.t()),
  options :: bulk_create_options()
) ::
  :ok
  | {:ok, Enumerable.t(Ash.Resource.record())}
  | {:error, Ash.Error.t()}
  | {:error, :no_rollback, Ash.Error.t()}
@spec can?(feature(), Ash.Resource.t() | Spark.Dsl.t()) :: boolean()
Link to this function

create(resource, changeset)

View Source
@spec create(Ash.Resource.t(), Ash.Changeset.t()) ::
  {:ok, Ash.Resource.record()}
  | {:error, term()}
  | {:error, :no_rollback, term()}
@spec data_layer(Ash.Resource.t() | Spark.Dsl.t()) :: t() | nil

The data layer of the resource, or nil if it does not have one

Link to this function

data_layer_can?(resource, feature)

View Source
@spec data_layer_can?(Ash.Resource.t() | Spark.Dsl.t(), feature()) :: boolean()

Whether or not the data layer supports a specific feature

Link to this function

data_layer_functions(resource)

View Source
@spec data_layer_functions(Ash.Resource.t()) :: map()

Custom functions supported by the data layer of the resource

Link to this function

destroy(resource, changeset)

View Source
@spec destroy(Ash.Resource.t(), Ash.Changeset.t()) ::
  :ok | {:error, term()} | {:error, :no_rollback, term()}
Link to this function

destroy_query(query, changeset, opts)

View Source
@spec destroy_query(
  data_layer_query(),
  Ash.Changeset.t(),
  opts :: bulk_update_options()
) ::
  :ok
  | {:ok, Enumerable.t(Ash.Resource.record())}
  | {:error, Ash.Error.t()}
  | {:error, :no_rollback, Ash.Error.t()}
Link to this function

distinct(query, distinct, resource)

View Source
@spec distinct(data_layer_query(), Ash.Sort.t(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

distinct_sort(query, sort, resource)

View Source
@spec distinct_sort(data_layer_query(), Ash.Sort.t(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

filter(query, filter, resource)

View Source
@spec filter(data_layer_query(), Ash.Filter.t(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

in_transaction?(resource)

View Source
Link to this function

limit(query, limit, resource)

View Source
@spec limit(data_layer_query(), limit :: non_neg_integer(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

lock(query, lock_type, resource)

View Source
@spec lock(
  data_layer_query(),
  lock_type :: lock_type() | nil,
  resource :: Ash.Resource.t()
) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

offset(query, offset, resource)

View Source
@spec offset(data_layer_query(), offset :: non_neg_integer(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

prefer_lateral_join_for_many_to_many?(data_layer)

View Source
@spec prefer_lateral_join_for_many_to_many?(t()) :: boolean()

Whether or not lateral joins should be used for many to many relationships by default

Link to this function

resource_to_query(resource, api)

View Source
@spec resource_to_query(Ash.Resource.t(), Ash.Api.t()) :: data_layer_query()
Link to this function

return_query(query, resource)

View Source
@spec return_query(data_layer_query(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

rollback(resource, term)

View Source
@spec rollback(Ash.Resource.t() | [Ash.Resource.t()], term()) :: no_return()

Rolls back the current transaction

Link to this function

run_aggregate_query(query, aggregates, resource)

View Source
@spec run_aggregate_query(
  data_layer_query(),
  [Ash.Query.Aggregate.t()],
  Ash.Resource.t()
) :: {:ok, map()} | {:error, term()}
Link to this function

run_aggregate_query_with_lateral_join(query, aggregates, root_data, destination_resource, path)

View Source
Link to this function

run_query(query, central_resource)

View Source
@spec run_query(data_layer_query(), central_resource :: Ash.Resource.t()) ::
  {:ok, [Ash.Resource.record()]}
  | {:error, term()}
  | {:error, :no_rollback, term()}
Link to this function

run_query_with_lateral_join(query, root_data, destination_resource, path)

View Source
Link to this function

select(query, select, resource)

View Source
@spec select(data_layer_query(), select :: [atom()], Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

set_context(resource, query, map)

View Source
@spec set_context(Ash.Resource.t(), data_layer_query(), map()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

set_tenant(resource, query, term)

View Source
@spec set_tenant(Ash.Resource.t(), data_layer_query(), term()) ::
  {:ok, data_layer_query()} | {:error, term()}
Link to this function

sort(query, sort, resource)

View Source
@spec sort(data_layer_query(), Ash.Sort.t(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
@spec source(Ash.Resource.t()) :: String.t()
Link to this function

transaction(resource_or_resources, func, timeout \\ nil, reason \\ %{type: :custom, metadata: %{}})

View Source
@spec transaction(
  Ash.Resource.t() | [Ash.Resource.t()],
  (-> term()),
  nil | pos_integer(),
  reason :: transaction_reason()
) :: term()

Wraps the execution of the function in a transaction with the resource's data_layer

Link to this function

update(resource, changeset)

View Source
@spec update(Ash.Resource.t(), Ash.Changeset.t()) ::
  {:ok, Ash.Resource.record()}
  | {:error, term()}
  | {:error, :no_rollback, term()}
Link to this function

update_query(query, changeset, opts)

View Source
@spec update_query(
  data_layer_query(),
  Ash.Changeset.t(),
  opts :: bulk_update_options()
) ::
  :ok
  | {:ok, Enumerable.t(Ash.Resource.record())}
  | {:error, Ash.Error.t()}
  | {:error, :no_rollback, Ash.Error.t()}
Link to this function

upsert(resource, changeset, keys)

View Source
@spec upsert(Ash.Resource.t(), Ash.Changeset.t(), [atom()]) ::
  {:ok, Ash.Resource.record()} | {:error, term()}