mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
fix: stop gitignoring the mnesia data layer
This commit is contained in:
parent
e1f6d7bca0
commit
e565d753ce
2 changed files with 236 additions and 1 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -25,6 +25,6 @@ ash-*.tar
|
|||
# Ignoring Elixir Language Server
|
||||
.elixir_ls/
|
||||
|
||||
Mnesia.*
|
||||
/Mnesia.*
|
||||
|
||||
.DS_Store
|
||||
|
|
235
lib/ash/data_layer/mnesia.ex
Normal file
235
lib/ash/data_layer/mnesia.ex
Normal file
|
@ -0,0 +1,235 @@
|
|||
defmodule Ash.DataLayer.Mnesia do
|
||||
@moduledoc """
|
||||
An Mnesia backed Ash Datalayer.
|
||||
|
||||
In your application intialization, you will need to call `Mnesia.create_schema([node()])`.
|
||||
|
||||
Additionally, you will want to create your mnesia tables there.
|
||||
|
||||
This data layer is *extremely unoptimized*, fetching all records from a table and filtering them
|
||||
in place. This is primarily used for testing the behavior of data layers in Ash. If it was improved,
|
||||
it could be a viable data layer.
|
||||
"""
|
||||
|
||||
alias Ash.DataLayer.Ets
|
||||
alias :mnesia, as: Mnesia
|
||||
|
||||
def start(api) do
|
||||
Mnesia.create_schema([node()])
|
||||
Mnesia.start()
|
||||
|
||||
Code.ensure_compiled(api)
|
||||
|
||||
api
|
||||
|> Ash.Api.resources()
|
||||
|> Enum.each(fn resource ->
|
||||
attributes = resource |> Ash.attributes() |> Enum.map(& &1.name) |> Enum.sort()
|
||||
|
||||
case Ash.primary_key(resource) do
|
||||
[] ->
|
||||
resource
|
||||
|> table()
|
||||
|> Mnesia.create_table(attributes: attributes)
|
||||
|
||||
_ ->
|
||||
resource
|
||||
|> table()
|
||||
|> Mnesia.create_table(attributes: [:_pkey | attributes])
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
alias Ash.Filter.Predicate.{Eq, GreaterThan, In, LessThan}
|
||||
|
||||
@behaviour Ash.DataLayer
|
||||
|
||||
@mnesia %Ash.Dsl.Section{
|
||||
name: :mnesia,
|
||||
describe: """
|
||||
A section for configuring the mnesia data layer
|
||||
""",
|
||||
schema: [
|
||||
table: [
|
||||
type: :atom,
|
||||
doc: "The table name to use, defaults to the name of the resource"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
use Ash.Dsl.Extension, sections: [@mnesia]
|
||||
|
||||
alias Ash.Dsl.Extension
|
||||
|
||||
def table(resource) do
|
||||
Extension.get_opt(resource, [:ets], :private?, resource, true)
|
||||
end
|
||||
|
||||
defmodule Query do
|
||||
@moduledoc false
|
||||
defstruct [:resource, :filter, :limit, :sort, relationships: %{}, offset: 0]
|
||||
end
|
||||
|
||||
@impl true
|
||||
def can?(_, :async_engine), do: true
|
||||
def can?(_, :composite_primary_key), do: true
|
||||
def can?(_, :upsert), do: true
|
||||
def can?(_, :boolean_filter), do: true
|
||||
def can?(_, :transact), do: true
|
||||
def can?(_, {:filter_predicate, _, %In{}}), do: true
|
||||
def can?(_, {:filter_predicate, _, %Eq{}}), do: true
|
||||
def can?(_, {:filter_predicate, _, %LessThan{}}), do: true
|
||||
def can?(_, {:filter_predicate, _, %GreaterThan{}}), do: true
|
||||
def can?(_, _), do: false
|
||||
|
||||
@impl true
|
||||
def resource_to_query(resource) do
|
||||
%Query{
|
||||
resource: resource
|
||||
}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def in_transaction?(_), do: Mnesia.is_transaction()
|
||||
|
||||
@impl true
|
||||
def limit(query, offset, _), do: {:ok, %{query | limit: offset}}
|
||||
|
||||
@impl true
|
||||
def offset(query, offset, _), do: {:ok, %{query | offset: offset}}
|
||||
|
||||
@impl true
|
||||
def filter(query, filter, _resource) do
|
||||
{:ok, %{query | filter: filter}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def sort(query, sort, _resource) do
|
||||
{:ok, %{query | sort: sort}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def run_query(
|
||||
%Query{resource: resource, filter: filter, offset: offset, limit: limit, sort: sort},
|
||||
_resource
|
||||
) do
|
||||
records =
|
||||
if Mnesia.is_transaction() do
|
||||
Mnesia.select(table(resource), [{:_, [], [:"$_"]}])
|
||||
else
|
||||
Mnesia.dirty_select(table(resource), [{:_, [], [:"$_"]}])
|
||||
end
|
||||
|
||||
attributes = resource |> Ash.attributes() |> Enum.map(& &1.name) |> Enum.sort()
|
||||
|
||||
elements_to_drop =
|
||||
case Ash.primary_key(resource) do
|
||||
[] ->
|
||||
1
|
||||
|
||||
_ ->
|
||||
2
|
||||
end
|
||||
|
||||
structified_records =
|
||||
Enum.map(records, fn record ->
|
||||
struct(resource, Enum.zip(attributes, Enum.drop(Tuple.to_list(record), elements_to_drop)))
|
||||
end)
|
||||
|
||||
offset_records =
|
||||
structified_records
|
||||
|> Ets.filter_matches(filter)
|
||||
|> Ets.do_sort(sort)
|
||||
|> Enum.drop(offset || 0)
|
||||
|
||||
limited_records =
|
||||
if limit do
|
||||
Enum.take(offset_records, limit)
|
||||
else
|
||||
offset_records
|
||||
end
|
||||
|
||||
{:ok, limited_records}
|
||||
rescue
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def create(resource, changeset) do
|
||||
record = Ecto.Changeset.apply_changes(changeset)
|
||||
|
||||
pkey =
|
||||
resource
|
||||
|> Ash.primary_key()
|
||||
|> Enum.map(fn attr ->
|
||||
Map.get(record, attr)
|
||||
end)
|
||||
|
||||
values =
|
||||
resource
|
||||
|> Ash.attributes()
|
||||
|> Enum.sort_by(& &1.name)
|
||||
|> Enum.map(&Map.get(record, &1.name))
|
||||
|
||||
values_with_primary_key =
|
||||
case pkey do
|
||||
[] ->
|
||||
List.to_tuple([table(resource) | values])
|
||||
|
||||
pkey_values ->
|
||||
List.to_tuple([table(resource) | [pkey_values | values]])
|
||||
end
|
||||
|
||||
if Mnesia.is_transaction() do
|
||||
Mnesia.write(values_with_primary_key)
|
||||
else
|
||||
Mnesia.dirty_write(values_with_primary_key)
|
||||
end
|
||||
|
||||
{:ok, record}
|
||||
rescue
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def destroy(%resource{} = record) do
|
||||
pkey =
|
||||
resource
|
||||
|> Ash.primary_key()
|
||||
|> Enum.map(&Map.get(record, &1))
|
||||
|
||||
if Mnesia.is_transaction() do
|
||||
Mnesia.delete({table(resource), pkey})
|
||||
else
|
||||
Mnesia.dirty_delete({table(resource), pkey})
|
||||
end
|
||||
rescue
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def update(resource, changeset) do
|
||||
create(resource, changeset)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def upsert(resource, changeset) do
|
||||
create(resource, changeset)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def transaction(_, func) do
|
||||
case Mnesia.transaction(func) do
|
||||
{:atomic, result} -> {:ok, result}
|
||||
{:aborted, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec rollback(term, term) :: no_return
|
||||
def rollback(_, value) do
|
||||
Mnesia.abort(value)
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue