# `Ash.DataLayer`
[🔗](https://github.com/ash-project/ash/blob/v3.23.1/lib/ash/data_layer/data_layer.ex#L5)

The behaviour for backing resource actions with persistence layers.

# `bulk_create_options`

```elixir
@type bulk_create_options() :: %{
  batch_size: pos_integer(),
  return_records?: boolean(),
  upsert?: boolean(),
  action_select: [atom()],
  upsert_keys: nil | [atom()],
  upsert_condition: Ash.Expr.t() | nil,
  return_skipped_upsert?: boolean(),
  identity: Ash.Resource.Identity.t() | nil,
  select: [atom()],
  upsert_fields:
    nil
    | [atom()]
    | :replace_all
    | {:replace, [atom()]}
    | {:replace_all_except, [atom()]},
  touch_update_defaults?: boolean(),
  tenant: term()
}
```

# `bulk_update_options`

```elixir
@type bulk_update_options() :: %{
  return_records?: boolean(),
  action_select: [atom()],
  calculations: [{Ash.Query.Calculation.t(), Ash.Expr.t()}],
  select: [atom()],
  tenant: term()
}
```

# `combination_type`

```elixir
@type combination_type() :: :union | :union_all | :intersection
```

# `data_layer_query`

```elixir
@type data_layer_query() :: struct()
```

# `feature`

```elixir
@type feature() ::
  :transact
  | :multitenancy
  | :combine
  | {:combine, combination_type()}
  | {:atomic, :update}
  | {:atomic, :upsert}
  | {:atomic, :create}
  | {:exists, :unrelated}
  | {:lateral_join, [Ash.Resource.t()]}
  | {:join, Ash.Resource.t()}
  | {:aggregate, :unrelated}
  | {:aggregate, Ash.Query.Aggregate.kind()}
  | {:aggregate_relationship, Ash.Resource.Relationships.relationship()}
  | {:query_aggregate, Ash.Query.Aggregate.kind()}
  | :select
  | :expr_error
  | :calculate
  | :expression_calculation
  | :expression_calculation_sort
  | :aggregate_filter
  | :aggregate_sort
  | :boolean_filter
  | :async_engine
  | :bulk_create
  | :bulk_create_with_partial_success
  | :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
  | :bulk_upsert_return_skipped
```

# `lateral_join_link`

```elixir
@type lateral_join_link() ::
  {Ash.Resource.t(), atom(), atom(), Ash.Resource.Relationships.relationship()}
```

# `lock_type`

```elixir
@type lock_type() :: :for_update | term()
```

# `t`

```elixir
@type t() :: module()
```

# `transaction_reason`

```elixir
@type transaction_reason() ::
  %{
    :type =&gt; :create,
    :metadata =&gt; %{resource: Ash.Resource.t(), action: atom()},
    optional(:data_layer_context) =&gt; %{}
  }
  | %{
      :type =&gt; :update,
      :metadata =&gt; %{
        resource: Ash.Resource.t(),
        action: atom(),
        record: Ash.Resource.record(),
        actor: term()
      },
      optional(:data_layer_context) =&gt; %{}
    }
  | %{
      :type =&gt; :destroy,
      :metadata =&gt; %{
        resource: Ash.Resource.t(),
        action: atom(),
        record: Ash.Resource.record(),
        actor: term()
      },
      optional(:data_layer_context) =&gt; %{}
    }
  | %{
      :type =&gt; :read,
      :metadata =&gt; %{
        resource: Ash.Resource.t(),
        query: Ash.Query.t(),
        actor: term()
      },
      optional(:data_layer_context) =&gt; %{}
    }
  | %{
      :type =&gt; :flow_transaction,
      :metadata =&gt; %{
        resource: Ash.Resource.t(),
        input: Ash.ActionInput.t(),
        action: atom(),
        actor: term()
      },
      optional(:data_layer_context) =&gt; %{}
    }
  | %{
      :type =&gt; :generic,
      :metadata =&gt; %{
        step_name: atom() | [term()],
        flow: module(),
        actor: term()
      },
      optional(:data_layer_context) =&gt; %{}
    }
  | %{type: :custom, metadata: map()}
  | %{type: atom(), metadata: map()}
```

# `add_aggregate`
*optional* 

```elixir
@callback add_aggregate(
  data_layer_query(),
  Ash.Query.Aggregate.t(),
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `add_aggregates`
*optional* 

```elixir
@callback add_aggregates(
  data_layer_query(),
  [Ash.Query.Aggregate.t()],
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `add_calculation`
*optional* 

```elixir
@callback add_calculation(
  data_layer_query(),
  Ash.Query.Calculation.t(),
  expression :: any(),
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `add_calculations`
*optional* 

```elixir
@callback add_calculations(
  data_layer_query(),
  [{Ash.Query.Calculation.t(), expression :: any()}],
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `bulk_create`
*optional* 

```elixir
@callback bulk_create(
  Ash.Resource.t(),
  Enumerable.t(Ash.Changeset.t()),
  options :: bulk_create_options()
) ::
  :ok
  | {:ok, Enumerable.t(Ash.Resource.record())}
  | {:partial_success,
     failed :: Enumerable.t({error :: term(), changeset :: Ash.Changeset.t()}),
     Enumerable.t(Ash.Resource.record())}
  | {:error, Ash.Error.t()}
  | {:error, :no_rollback, Ash.Error.t()}
```

# `calculate`
*optional* 

```elixir
@callback calculate(Ash.Resource.t(), [Ash.Expr.t()], context :: map()) ::
  {:ok, term()} | {:error, term()}
```

# `can?`

```elixir
@callback can?(Ash.Resource.t() | Spark.Dsl.t(), feature()) :: boolean()
```

# `combination_acc`
*optional* 

```elixir
@callback combination_acc(data_layer_query()) :: any()
```

# `combination_of`
*optional* 

```elixir
@callback combination_of(
  combine :: [{combination_type(), data_layer_query()}],
  resource :: Ash.Resource.t(),
  domain :: Ash.Domain.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `create`
*optional* 

```elixir
@callback create(Ash.Resource.t(), Ash.Changeset.t()) ::
  {:ok, Ash.Resource.record()}
  | {:error, term()}
  | {:error, :no_rollback, term()}
```

# `destroy`
*optional* 

```elixir
@callback destroy(Ash.Resource.t(), Ash.Changeset.t()) ::
  :ok | {:error, term()} | {:error, :no_rollback, term()}
```

# `destroy_query`
*optional* 

```elixir
@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()}
```

# `distinct`
*optional* 

```elixir
@callback distinct(data_layer_query(), [atom()], resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `distinct_sort`
*optional* 

```elixir
@callback distinct_sort(data_layer_query(), Ash.Sort.t(), resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `filter`
*optional* 

```elixir
@callback filter(data_layer_query(), Ash.Filter.t(), resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `functions`
*optional* 

```elixir
@callback functions(Ash.Resource.t()) :: [module()]
```

# `in_transaction?`
*optional* 

```elixir
@callback in_transaction?(Ash.Resource.t()) :: boolean()
```

# `limit`
*optional* 

```elixir
@callback limit(
  data_layer_query(),
  limit :: non_neg_integer(),
  resource :: Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `lock`
*optional* 

```elixir
@callback lock(data_layer_query(), lock_type(), resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `offset`
*optional* 

```elixir
@callback offset(
  data_layer_query(),
  offset :: non_neg_integer(),
  resource :: Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `prefer_lateral_join_for_many_to_many?`
*optional* 

```elixir
@callback prefer_lateral_join_for_many_to_many?() :: boolean()
```

# `prefer_transaction?`
*optional* 

```elixir
@callback prefer_transaction?(Ash.Resource.t()) :: boolean()
```

# `prefer_transaction_for_atomic_updates?`
*optional* 

```elixir
@callback prefer_transaction_for_atomic_updates?(Ash.Resource.t()) :: boolean()
```

# `resource_to_query`

```elixir
@callback resource_to_query(Ash.Resource.t(), Ash.Domain.t()) :: data_layer_query()
```

# `return_query`
*optional* 

```elixir
@callback return_query(data_layer_query(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `rollback`
*optional* 

```elixir
@callback rollback(Ash.Resource.t(), term()) :: no_return()
```

# `run_aggregate_query`
*optional* 

```elixir
@callback run_aggregate_query(
  data_layer_query(),
  [Ash.Query.Aggregate.t()],
  Ash.Resource.t()
) :: {:ok, map()} | {:error, term()}
```

# `run_aggregate_query_with_lateral_join`
*optional* 

```elixir
@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()}
```

# `run_query`
*optional* 

```elixir
@callback run_query(data_layer_query(), Ash.Resource.t()) ::
  {:ok, [Ash.Resource.record()]}
  | {:error, term()}
  | {:error, :no_rollback, term()}
```

# `run_query_with_lateral_join`
*optional* 

```elixir
@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()}
```

# `select`
*optional* 

```elixir
@callback select(
  data_layer_query(),
  select :: [atom()],
  resource :: Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `set_context`
*optional* 

```elixir
@callback set_context(Ash.Resource.t(), data_layer_query(), map()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `set_tenant`
*optional* 

```elixir
@callback set_tenant(Ash.Resource.t(), data_layer_query(), term()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `sort`
*optional* 

```elixir
@callback sort(data_layer_query(), Ash.Sort.t(), resource :: Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `source`
*optional* 

```elixir
@callback source(Ash.Resource.t()) :: String.t()
```

# `transaction`
*optional* 

```elixir
@callback transaction(
  Ash.Resource.t(),
  (-&gt; term()),
  nil | pos_integer(),
  reason :: transaction_reason()
) :: {:ok, term()} | {:error, term()}
```

# `transform_query`
*optional* 

```elixir
@callback transform_query(Ash.Query.t()) :: Ash.Query.t()
```

# `update`
*optional* 

```elixir
@callback update(Ash.Resource.t(), Ash.Changeset.t()) ::
  {:ok, Ash.Resource.record()}
  | {:error, term()}
  | {:error, :no_rollback, term()}
```

# `update_query`
*optional* 

```elixir
@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()}
```

# `upsert`
*optional* 

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

# `upsert`
*optional* 

```elixir
@callback upsert(
  Ash.Resource.t(),
  Ash.Changeset.t(),
  [atom()],
  Ash.Resource.Identity.t() | nil
) ::
  {:ok,
   Ash.Resource.record()
   | {:upsert_skipped, Ash.Query.t(),
      (-&gt; {:ok, Ash.Resource.record()}
          | {:error, term()}
          | {:error, :no_rollback, term()})}}
  | {:error, term()}
  | {:error, :no_rollback, term()}
```

# `add_aggregates`

```elixir
@spec add_aggregates(data_layer_query(), [Ash.Query.Aggregate.t()], Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `add_calculations`

```elixir
@spec add_calculations(
  data_layer_query(),
  [{Ash.Query.Calculation.t(), expression :: term()}],
  Ash.Resource.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `bulk_create`

```elixir
@spec bulk_create(
  Ash.Resource.t(),
  Enumerable.t(Ash.Changeset.t()),
  options :: bulk_create_options()
) ::
  :ok
  | {:ok, Enumerable.t(Ash.Resource.record())}
  | {:partial_success,
     failed :: Enumerable.t({error :: term(), changeset :: Ash.Changeset.t()}),
     Enumerable.t(Ash.Resource.record())}
  | {:error, Ash.Error.t()}
  | {:error, :no_rollback, Ash.Error.t()}
```

# `calculate`

```elixir
@spec calculate(Ash.Resource.t(), [Ash.Expr.t()], context :: map()) ::
  {:ok, [term()]} | {:error, Ash.Error.t()}
```

# `can?`

```elixir
@spec can?(feature(), Ash.Resource.t() | Spark.Dsl.t()) :: boolean()
```

# `combination_acc`

```elixir
@spec combination_acc(data_layer_query(), Ash.Resource.t()) :: any()
```

# `combination_of`

```elixir
@spec combination_of(
  [{combination_type(), data_layer_query()}],
  resource :: Ash.Resource.t(),
  domain :: Ash.Domain.t()
) :: {:ok, data_layer_query()} | {:error, term()}
```

# `create`

```elixir
@spec create(Ash.Resource.t(), Ash.Changeset.t()) ::
  {:ok, Ash.Resource.record()}
  | {:error, term()}
  | {:error, :no_rollback, term()}
```

# `data_layer`

```elixir
@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

# `data_layer_can?`

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

Whether or not the data layer supports a specific feature

# `data_layer_functions`

```elixir
@spec data_layer_functions(Ash.Resource.t()) :: map()
```

Custom functions supported by the data layer of the resource

# `destroy`

```elixir
@spec destroy(Ash.Resource.t(), Ash.Changeset.t()) ::
  :ok | {:error, term()} | {:error, :no_rollback, term()}
```

# `destroy_query`

```elixir
@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()}
```

# `distinct`

```elixir
@spec distinct(data_layer_query(), Ash.Sort.t(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `distinct_sort`

```elixir
@spec distinct_sort(data_layer_query(), Ash.Sort.t(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `filter`

```elixir
@spec filter(data_layer_query(), Ash.Filter.t(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `functions`

# `in_transaction?`

# `limit`

```elixir
@spec limit(data_layer_query(), limit :: non_neg_integer(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `lock`

```elixir
@spec lock(
  data_layer_query(),
  lock_type :: lock_type() | nil,
  resource :: Ash.Resource.t()
) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `offset`

```elixir
@spec offset(data_layer_query(), offset :: non_neg_integer(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `prefer_lateral_join_for_many_to_many?`

```elixir
@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

# `prefer_transaction?`

```elixir
@spec prefer_transaction?(Ash.Resource.t()) :: boolean()
```

# `prefer_transaction_for_atomic_updates?`

```elixir
@spec prefer_transaction_for_atomic_updates?(Ash.Resource.t()) :: boolean()
```

# `resource_to_query`

```elixir
@spec resource_to_query(Ash.Resource.t(), Ash.Domain.t()) :: data_layer_query()
```

# `return_query`

```elixir
@spec return_query(data_layer_query(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `rollback`

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

Rolls back the current transaction

# `run_aggregate_query`

```elixir
@spec run_aggregate_query(
  data_layer_query(),
  [Ash.Query.Aggregate.t()],
  Ash.Resource.t()
) :: {:ok, map()} | {:error, term()}
```

# `run_aggregate_query_with_lateral_join`

# `run_query`

```elixir
@spec run_query(data_layer_query(), central_resource :: Ash.Resource.t()) ::
  {:ok, [Ash.Resource.record()]}
  | {:error, term()}
  | {:error, :no_rollback, term()}
```

# `run_query_with_lateral_join`

# `select`

```elixir
@spec select(data_layer_query(), select :: [atom()], Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `set_context`

```elixir
@spec set_context(Ash.Resource.t(), data_layer_query(), map()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `set_tenant`

```elixir
@spec set_tenant(Ash.Resource.t(), data_layer_query(), String.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `sort`

```elixir
@spec sort(data_layer_query(), Ash.Sort.t(), Ash.Resource.t()) ::
  {:ok, data_layer_query()} | {:error, term()}
```

# `source`

```elixir
@spec source(Ash.Resource.t()) :: String.t()
```

# `transaction`

```elixir
@spec transaction(
  Ash.Resource.t() | [Ash.Resource.t()],
  (-&gt; term()),
  nil | pos_integer(),
  reason :: transaction_reason(),
  opts :: Keyword.t()
) :: term()
```

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

# `transform_query`

# `update`

```elixir
@spec update(Ash.Resource.t(), Ash.Changeset.t()) ::
  {:ok, Ash.Resource.record()}
  | {:error, term()}
  | {:error, :no_rollback, term()}
```

# `update_query`

```elixir
@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()}
```

# `upsert`

```elixir
@spec upsert(
  Ash.Resource.t(),
  Ash.Changeset.t(),
  [atom()],
  identity :: Ash.Resource.Identity.t() | nil
) ::
  {:ok,
   Ash.Resource.record()
   | {:upsert_skipped, Ash.Query.t(),
      (-&gt; {:ok, Ash.Resource.record()}
          | {:error, term()}
          | {:error, :no_rollback, term()})}}
  | {:error, term()}
  | {:error, :no_rollback, term()}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
