Current section

Files

Jump to
aarondb src aarondb_mnesia_ffi.erl
Raw

src/aarondb_mnesia_ffi.erl

-module(aarondb_mnesia_ffi).
-export([init/0, persist/1, persist_batch/1, recover/0, select/1]).
init() ->
case mnesia:system_info(is_running) of
yes -> ok;
_ ->
_ = mnesia:create_schema([node()]),
application:ensure_all_started(mnesia)
end,
ExpectedAttrs = [entity, attribute, value, tx, tx_index, valid_time, operation],
case table_attributes(datoms) of
{ok, ExpectedAttrs} ->
{ok, nil};
{ok, ActualAttrs} ->
{error, schema_mismatch_message(ExpectedAttrs, ActualAttrs)};
missing ->
create_datoms_table(ExpectedAttrs)
end.
table_attributes(Table) ->
try mnesia:table_info(Table, attributes) of
Attrs -> {ok, Attrs}
catch
exit:{aborted, _} -> missing
end.
create_datoms_table(ExpectedAttrs) ->
case mnesia:create_table(datoms, [
{record_name, datom},
{attributes, ExpectedAttrs},
{disc_copies, [node()]}
]) of
{atomic, ok} ->
wait_for_datoms();
{aborted, {already_exists, datoms}} ->
%% A concurrent initializer may have created it; inspect rather than
%% assuming its schema matches.
case table_attributes(datoms) of
{ok, ExpectedAttrs} -> wait_for_datoms();
{ok, ActualAttrs} -> {error, schema_mismatch_message(ExpectedAttrs, ActualAttrs)};
missing -> {error, <<"datoms table disappeared during initialization">>}
end;
{aborted, Reason} ->
{error, list_to_binary(io_lib:format("failed to create datoms table: ~p", [Reason]))}
end.
wait_for_datoms() ->
case mnesia:wait_for_tables([datoms], 5000) of
ok -> {ok, nil};
{timeout, Tables} ->
{error, list_to_binary(io_lib:format("timed out waiting for Mnesia tables: ~p", [Tables]))};
{error, Reason} ->
{error, list_to_binary(io_lib:format("failed waiting for Mnesia tables: ~p", [Reason]))}
end.
schema_mismatch_message(ExpectedAttrs, ActualAttrs) ->
list_to_binary(
io_lib:format(
"datoms schema mismatch; expected ~p, found ~p. No data was modified. Create a backup, migrate explicitly, or reset the table deliberately.",
[ExpectedAttrs, ActualAttrs]
)
).
persist(Datom) ->
mnesia:dirty_write(datoms, Datom),
nil.
persist_batch(Datoms) ->
F = fun() ->
lists:foreach(fun(D) -> mnesia:write(datoms, D, write) end, Datoms)
end,
mnesia:transaction(F),
nil.
recover() ->
F = fun() ->
mnesia:match_object(datoms, {datom, '_', '_', '_', '_', '_', '_', '_'}, read)
end,
case mnesia:transaction(F) of
{atomic, Records} -> {ok, Records};
{aborted, Reason} -> {error, list_to_binary(io_lib:format("~p", [Reason]))}
end.
select(Pattern) ->
{E, A, V} = Pattern,
MatchE = case E of {uid, {entity_id, Eid}} -> Eid; _ -> '_' end,
MatchA = A, % A is already a binary in Erlang from Gleam String
MatchV = case V of {val, Val} -> Val; _ -> '_' end,
MS = {datom, MatchE, MatchA, MatchV, '_', '_', '_', '_'},
F = fun() -> mnesia:match_object(datoms, MS, read) end,
case mnesia:transaction(F) of
{atomic, Records} -> {ok, Records};
{aborted, Reason} -> {error, list_to_binary(io_lib:format("~p", [Reason]))}
end.