View Source Ash.DataLayer behaviour (ash v2.14.17)
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
Wether 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 data_layer_query() :: struct()
@type feature() :: :transact | :multitenancy | {:atomic, :update} | {: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 | :aggregate_filter | :aggregate_sort | :boolean_filter | :async_engine | :create | :read | :update | :destroy | :limit | :offset | :transact | :filter | {: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()}} | %{ type: :update, metadata: %{ resource: Ash.Resource.t(), action: atom(), record: Ash.Resource.record(), actor: term() } } | %{ type: :destroy, metadata: %{ resource: Ash.Resource.t(), action: atom(), record: Ash.Resource.record(), actor: term() } } | %{ type: :read, metadata: %{ resource: Ash.Resource.t(), query: Ash.Query.t(), actor: term() } } | %{ type: :flow_transaction, metadata: %{step_name: atom() | [term()], flow: module(), actor: term()} } | %{type: :custom, metadata: map()} | %{type: atom(), metadata: map()}
Callbacks
@callback add_aggregate( data_layer_query(), Ash.Query.Aggregate.t(), Ash.Resource.t() ) :: {:ok, data_layer_query()} | {:error, term()}
@callback add_aggregates( data_layer_query(), [Ash.Query.Aggregate.t()], Ash.Resource.t() ) :: {:ok, data_layer_query()} | {:error, term()}
@callback add_calculation( data_layer_query(), Ash.Query.Calculation.t(), expression :: any(), Ash.Resource.t() ) :: {:ok, data_layer_query()} | {:error, term()}
@callback add_calculations( data_layer_query(), [{Ash.Query.Calculation.t(), expression :: any()}], Ash.Resource.t() ) :: {:ok, data_layer_query()} | {:error, term()}
@callback bulk_create( Ash.Resource.t(), Enumerable.t(Ash.Changeset.t()), options :: bulk_options() ) :: {:ok, Enumerable.t(:ok | {:ok, Ash.Resource.record()} | {:error, Ash.Error.t()})} | {:error, 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()}
@callback destroy(Ash.Resource.t(), Ash.Changeset.t()) :: :ok | {:error, term()}
@callback distinct(data_layer_query(), [atom()], resource :: Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@callback distinct_sort(data_layer_query(), Ash.Sort.t(), resource :: Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@callback filter(data_layer_query(), Ash.Filter.t(), resource :: Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@callback functions(Ash.Resource.t()) :: [module()]
@callback in_transaction?(Ash.Resource.t()) :: boolean()
@callback limit( data_layer_query(), limit :: non_neg_integer(), resource :: Ash.Resource.t() ) :: {:ok, data_layer_query()} | {:error, term()}
@callback lock(data_layer_query(), lock_type(), resource :: Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@callback offset( data_layer_query(), offset :: non_neg_integer(), resource :: Ash.Resource.t() ) :: {:ok, data_layer_query()} | {:error, term()}
@callback prefer_lateral_join_for_many_to_many?() :: boolean()
@callback resource_to_query(Ash.Resource.t(), Ash.Api.t()) :: data_layer_query()
@callback rollback(Ash.Resource.t(), term()) :: no_return()
@callback run_aggregate_query( data_layer_query(), [Ash.Query.Aggregate.t()], Ash.Resource.t() ) :: {:ok, map()} | {:error, term()}
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()}
@callback run_query(data_layer_query(), Ash.Resource.t()) :: {:ok, [Ash.Resource.record()]} | {:error, term()}
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()}
@callback select( data_layer_query(), select :: [atom()], resource :: Ash.Resource.t() ) :: {:ok, data_layer_query()} | {:error, term()}
@callback set_context(Ash.Resource.t(), data_layer_query(), map()) :: {:ok, data_layer_query()} | {:error, term()}
@callback set_tenant(Ash.Resource.t(), data_layer_query(), term()) :: {:ok, data_layer_query()} | {:error, term()}
@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()
@callback transaction( Ash.Resource.t(), (-> term()), nil | pos_integer(), reason :: transaction_reason() ) :: {:ok, term()} | {:error, term()}
@callback transform_query(Ash.Query.t()) :: Ash.Query.t()
@callback update(Ash.Resource.t(), Ash.Changeset.t()) :: {:ok, Ash.Resource.record()} | {:error, term()}
@callback upsert(Ash.Resource.t(), Ash.Changeset.t(), [atom()]) :: {:ok, Ash.Resource.record()} | {:error, term()}
Functions
@spec add_aggregates(data_layer_query(), [Ash.Query.Aggregate.t()], Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@spec add_calculations( data_layer_query(), [{Ash.Query.Calculation.t(), expression :: term()}], Ash.Resource.t() ) :: {:ok, data_layer_query()} | {:error, term()}
@spec bulk_create( Ash.Resource.t(), Enumerable.t(Ash.Changeset.t()), options :: bulk_options() ) :: :ok | {:ok, Enumerable.t(Ash.Resource.record())} | {:error, Ash.Error.t()}
@spec can?(feature(), Ash.Resource.t() | Spark.Dsl.t()) :: boolean()
@spec create(Ash.Resource.t(), Ash.Changeset.t()) :: {:ok, Ash.Resource.record()} | {:error, 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
@spec data_layer_can?(Ash.Resource.t() | Spark.Dsl.t(), feature()) :: boolean()
Whether or not the data layer supports a specific feature
@spec data_layer_functions(Ash.Resource.t()) :: map()
Custom functions supported by the data layer of the resource
@spec destroy(Ash.Resource.t(), Ash.Changeset.t()) :: :ok | {:error, term()}
@spec distinct(data_layer_query(), Ash.Sort.t(), Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@spec distinct_sort(data_layer_query(), Ash.Sort.t(), Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@spec filter(data_layer_query(), Ash.Filter.t(), Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@spec limit(data_layer_query(), limit :: non_neg_integer(), Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@spec lock( data_layer_query(), lock_type :: lock_type() | nil, resource :: Ash.Resource.t() ) :: {:ok, data_layer_query()} | {:error, term()}
@spec offset(data_layer_query(), offset :: non_neg_integer(), Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
Wether or not lateral joins should be used for many to many relationships by default
@spec resource_to_query(Ash.Resource.t(), Ash.Api.t()) :: data_layer_query()
@spec rollback(Ash.Resource.t(), term()) :: no_return()
Rolls back the current transaction
@spec run_aggregate_query( data_layer_query(), [Ash.Query.Aggregate.t()], Ash.Resource.t() ) :: {:ok, map()} | {:error, term()}
run_aggregate_query_with_lateral_join(query, aggregates, root_data, destination_resource, path)
View Source@spec run_query(data_layer_query(), central_resource :: Ash.Resource.t()) :: {:ok, [Ash.Resource.record()]} | {:error, term()}
run_query_with_lateral_join(query, root_data, destination_resource, path)
View Source@spec select(data_layer_query(), select :: [atom()], Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@spec set_context(Ash.Resource.t(), data_layer_query(), map()) :: {:ok, data_layer_query()} | {:error, term()}
@spec set_tenant(Ash.Resource.t(), data_layer_query(), term()) :: {:ok, data_layer_query()} | {:error, term()}
@spec sort(data_layer_query(), Ash.Sort.t(), Ash.Resource.t()) :: {:ok, data_layer_query()} | {:error, term()}
@spec source(Ash.Resource.t()) :: String.t()
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
@spec update(Ash.Resource.t(), Ash.Changeset.t()) :: {:ok, Ash.Resource.record()} | {:error, term()}
@spec upsert(Ash.Resource.t(), Ash.Changeset.t(), [atom()]) :: {:ok, Ash.Resource.record()} | {:error, term()}