Packages

An embedded, crash-safe key-value store for Gleam, inspired by CubDB

Current section

Files

Jump to
trove src trove@internal@db.erl
Raw

src/trove@internal@db.erl

-module(trove@internal@db).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/trove/internal/db.gleam").
-export([get/3, put/4, delete/3, has_key/3, put_multi/3, delete_multi/3, put_and_delete_multi/4, transaction/3, acquire_snapshot/2, compact/2, size/2, dirt_factor/2, file_sync/2, set_auto_compact/3, close/2, open/7]).
-export_type([internal_open_error/0, file_sync/0, auto_compact/0, transaction_outcome/2, message/2, db_state/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(false).
-type internal_open_error() :: {internal_directory_error, binary()} |
{internal_store_error, binary()} |
{internal_lock_error, binary()} |
internal_actor_start_error.
-type file_sync() :: auto_sync | manual_sync.
-type auto_compact() :: {auto_compact, integer(), float()} | no_auto_compact.
-type transaction_outcome(KBA, KBB) :: {commit_outcome,
trove@internal@btree:btree(KBA, KBB)} |
cancel_outcome.
-type message(KBC, KBD) :: {get,
KBC,
gleam@erlang@process:subject(gleam@option:option(KBD))} |
{put, KBC, KBD, gleam@erlang@process:subject(nil)} |
{delete, KBC, gleam@erlang@process:subject(nil)} |
{has_key, KBC, gleam@erlang@process:subject(boolean())} |
{put_multi, list({KBC, KBD}), gleam@erlang@process:subject(nil)} |
{delete_multi, list(KBC), gleam@erlang@process:subject(nil)} |
{put_and_delete_multi,
list({KBC, KBD}),
list(KBC),
gleam@erlang@process:subject(nil)} |
{execute_transaction,
fun((trove@internal@tx:tx(KBC, KBD)) -> transaction_outcome(KBC, KBD)),
gleam@erlang@process:subject(nil)} |
{acquire_snapshot,
gleam@erlang@process:subject({ok,
trove@internal@snapshot:snapshot(KBC, KBD)} |
{error, binary()})} |
{compact, gleam@erlang@process:subject({ok, nil} | {error, binary()})} |
{size, gleam@erlang@process:subject(integer())} |
{dirt_factor, gleam@erlang@process:subject(float())} |
{sync_file, gleam@erlang@process:subject(nil)} |
{set_auto_compact, auto_compact(), gleam@erlang@process:subject(nil)} |
{close, gleam@erlang@process:subject(nil)}.
-type db_state(KBE, KBF) :: {db_state,
trove@internal@btree:btree(KBE, KBF),
trove@internal@store:store(),
trove@codec:codec(KBE),
trove@codec:codec(KBF),
fun((KBE, KBE) -> gleam@order:order()),
file_sync(),
auto_compact(),
binary(),
integer(),
integer()}.
-file("src/trove/internal/db.gleam", 88).
?DOC(false).
-spec store_path(binary(), integer()) -> binary().
store_path(Dir, File_number) ->
<<<<<<Dir/binary, "/"/utf8>>/binary,
(erlang:integer_to_binary(File_number))/binary>>/binary,
".trv"/utf8>>.
-file("src/trove/internal/db.gleam", 92).
?DOC(false).
-spec parse_trv_number(binary()) -> {ok, integer()} | {error, nil}.
parse_trv_number(Name) ->
gleam@bool:guard(
not gleam_stdlib:string_ends_with(Name, <<".trv"/utf8>>),
{error, nil},
fun() -> _pipe = Name,
_pipe@1 = gleam@string:drop_end(_pipe, 4),
gleam_stdlib:parse_int(_pipe@1) end
).
-file("src/trove/internal/db.gleam", 97).
?DOC(false).
-spec compute_dirt_factor(trove@internal@btree:btree(any(), any())) -> float().
compute_dirt_factor(Tree) ->
case erlang:float(
(1 + trove@internal@btree:size(Tree)) + trove@internal@btree:dirt(Tree)
) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:float(trove@internal@btree:dirt(Tree)) / Gleam@denominator
end.
-file("src/trove/internal/db.gleam", 146).
?DOC(false).
-spec cleanup_old_store_files(binary(), integer()) -> nil.
cleanup_old_store_files(Path, Current_file_number) ->
case trove_file_ffi:list_dir(Path) of
{error, _} ->
nil;
{ok, Files} ->
Old_numbers = gleam@list:filter_map(
Files,
fun(Name) -> case parse_trv_number(Name) of
{ok, N} when N < Current_file_number ->
{ok, N};
_ ->
{error, nil}
end end
),
gleam@list:each(
Old_numbers,
fun(N@1) ->
Old_path = store_path(Path, N@1),
case trove_file_ffi:delete_file(Old_path) of
{ok, nil} ->
nil;
{error, Reason} ->
gleam_stdlib:println_error(
<<<<<<"[trove] failed to delete old store "/utf8,
Old_path/binary>>/binary,
": "/utf8>>/binary,
Reason/binary>>
)
end
end
)
end.
-file("src/trove/internal/db.gleam", 102).
?DOC(false).
-spec do_compact(db_state(KBM, KBN)) -> {ok, db_state(KBM, KBN)} |
{error, trove@internal@btree:btree_error()}.
do_compact(State) ->
New_file_number = erlang:element(10, State) + 1,
New_path = store_path(erlang:element(9, State), New_file_number),
Old_path = store_path(erlang:element(9, State), erlang:element(10, State)),
_ = trove_file_ffi:delete_file(New_path),
gleam@result:'try'(
trove@internal@compactor:compact(
erlang:element(2, State),
erlang:element(3, State),
New_path,
trove@internal@btree:capacity(erlang:element(2, State)),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State)
),
fun(_use0) ->
{New_tree, New_store} = _use0,
gleam@result:'try'(
begin
_pipe = trove_file_ffi:dir_fsync(erlang:element(9, State)),
gleam@result:map_error(
_pipe,
fun(S) -> {store_error, {io_error, S}} end
)
end,
fun(_) ->
case trove@internal@store:close(erlang:element(3, State)) of
{ok, nil} ->
nil;
{error, Reason} ->
gleam_stdlib:println_error(
<<"[trove] failed to close old store during compaction: "/utf8,
(trove@internal@store:error_to_string(
Reason
))/binary>>
)
end,
_ = trove_file_ffi:delete_file(Old_path),
cleanup_old_store_files(
erlang:element(9, State),
New_file_number
),
_ = trove_file_ffi:dir_fsync(erlang:element(9, State)),
{ok,
{db_state,
New_tree,
New_store,
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
New_file_number,
erlang:element(11, State)}}
end
)
end
).
-file("src/trove/internal/db.gleam", 174).
?DOC(false).
-spec maybe_auto_compact(db_state(KBU, KBV)) -> db_state(KBU, KBV).
maybe_auto_compact(State) ->
case erlang:element(8, State) of
no_auto_compact ->
State;
{auto_compact, Min_dirt, Min_dirt_factor} ->
Should_compact = (trove@internal@btree:dirt(
erlang:element(2, State)
)
>= Min_dirt)
andalso (compute_dirt_factor(erlang:element(2, State)) >= Min_dirt_factor),
gleam@bool:guard(
not Should_compact,
State,
fun() -> case do_compact(State) of
{ok, New_state} ->
New_state;
{error, Reason} ->
gleam_stdlib:println_error(
<<"[trove] auto-compact failed: "/utf8,
(trove@internal@btree:error_to_string(
Reason
))/binary>>
),
State
end end
)
end.
-file("src/trove/internal/db.gleam", 195).
?DOC(false).
-spec fold_inserts(
trove@internal@btree:btree(KCA, KCB),
list({KCA, KCB}),
db_state(KCA, KCB)
) -> trove@internal@btree:btree(KCA, KCB).
fold_inserts(Tree, Entries, State) ->
gleam@list:fold(
Entries,
Tree,
fun(Tree@1, Entry) ->
T@1 = case trove@internal@btree:insert(
Tree@1,
erlang:element(3, State),
erlang:element(1, Entry),
erlang:element(2, Entry),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State)
) of
{ok, T} -> T;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"fold_inserts"/utf8>>,
line => 201,
value => _assert_fail,
start => 5612,
'end' => 5864,
pattern_start => 5623,
pattern_end => 5628})
end,
T@1
end
).
-file("src/trove/internal/db.gleam", 215).
?DOC(false).
-spec fold_deletes(
trove@internal@btree:btree(KCJ, KCK),
list(KCJ),
db_state(KCJ, KCK)
) -> trove@internal@btree:btree(KCJ, KCK).
fold_deletes(Tree, Keys, State) ->
gleam@list:fold(
Keys,
Tree,
fun(Tree@1, Key) ->
T@1 = case trove@internal@btree:delete(
Tree@1,
erlang:element(3, State),
Key,
erlang:element(4, State),
erlang:element(6, State)
) of
{ok, T} -> T;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"fold_deletes"/utf8>>,
line => 221,
value => _assert_fail,
start => 6033,
'end' => 6217,
pattern_start => 6044,
pattern_end => 6049})
end,
T@1
end
).
-file("src/trove/internal/db.gleam", 427).
?DOC(false).
-spec write_header(
trove@internal@store:store(),
trove@internal@btree:btree(any(), any()),
file_sync()
) -> {ok, nil} | {error, trove@internal@store:store_error()}.
write_header(Store, Tree, Auto_sync) ->
Header = {header,
trove@internal@btree:root(Tree),
trove@internal@btree:size(Tree),
trove@internal@btree:dirt(Tree)},
gleam@result:'try'(
trove@internal@store:put_header(Store, Header),
fun(_) -> case Auto_sync of
auto_sync ->
trove@internal@store:sync(Store);
manual_sync ->
{ok, nil}
end end
).
-file("src/trove/internal/db.gleam", 233).
?DOC(false).
-spec commit_tree(
db_state(KCS, KCT),
trove@internal@btree:btree(KCS, KCT),
gleam@erlang@process:subject(nil)
) -> gleam@otp@actor:next(db_state(KCS, KCT), message(KCS, KCT)).
commit_tree(State, New_tree, Reply) ->
case (trove@internal@btree:root(erlang:element(2, State)) =:= trove@internal@btree:root(
New_tree
))
andalso (trove@internal@btree:size(erlang:element(2, State)) =:= trove@internal@btree:size(
New_tree
)) of
true ->
gleam@otp@actor:send(Reply, nil),
gleam@otp@actor:continue(State);
false ->
case write_header(
erlang:element(3, State),
New_tree,
erlang:element(7, State)
) of
{ok, nil} -> nil;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"commit_tree"/utf8>>,
line => 247,
value => _assert_fail,
start => 6620,
'end' => 6706,
pattern_start => 6631,
pattern_end => 6638})
end,
gleam@otp@actor:send(Reply, nil),
gleam@otp@actor:continue(
maybe_auto_compact(
{db_state,
New_tree,
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State)}
)
)
end.
-file("src/trove/internal/db.gleam", 255).
?DOC(false).
-spec handle_message(db_state(KDF, KDG), message(KDF, KDG)) -> gleam@otp@actor:next(db_state(KDF, KDG), message(KDF, KDG)).
handle_message(State, Msg) ->
case Msg of
{get, Key, Reply} ->
Result@1 = case trove@internal@btree:lookup(
erlang:element(2, State),
erlang:element(3, State),
Key,
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State)
) of
{ok, Result} -> Result;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"handle_message"/utf8>>,
line => 261,
value => _assert_fail,
start => 6981,
'end' => 7232,
pattern_start => 6992,
pattern_end => 7002})
end,
gleam@otp@actor:send(Reply, Result@1),
gleam@otp@actor:continue(State);
{put, Key@1, Value, Reply@1} ->
New_tree@1 = case trove@internal@btree:insert(
erlang:element(2, State),
erlang:element(3, State),
Key@1,
Value,
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State)
) of
{ok, New_tree} -> New_tree;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"handle_message"/utf8>>,
line => 275,
value => _assert_fail@1,
start => 7338,
'end' => 7615,
pattern_start => 7349,
pattern_end => 7361})
end,
commit_tree(State, New_tree@1, Reply@1);
{delete, Key@2, Reply@2} ->
New_tree@3 = case trove@internal@btree:delete(
erlang:element(2, State),
erlang:element(3, State),
Key@2,
erlang:element(4, State),
erlang:element(6, State)
) of
{ok, New_tree@2} -> New_tree@2;
_assert_fail@2 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"handle_message"/utf8>>,
line => 289,
value => _assert_fail@2,
start => 7699,
'end' => 7910,
pattern_start => 7710,
pattern_end => 7722})
end,
commit_tree(State, New_tree@3, Reply@2);
{has_key, Key@3, Reply@3} ->
Result@3 = case trove@internal@btree:contains(
erlang:element(2, State),
erlang:element(3, State),
Key@3,
erlang:element(4, State),
erlang:element(6, State)
) of
{ok, Result@2} -> Result@2;
_assert_fail@3 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"handle_message"/utf8>>,
line => 301,
value => _assert_fail@3,
start => 7994,
'end' => 8205,
pattern_start => 8005,
pattern_end => 8015})
end,
gleam@otp@actor:send(Reply@3, Result@3),
gleam@otp@actor:continue(State);
{put_multi, Entries, Reply@4} ->
New_tree@4 = fold_inserts(erlang:element(2, State), Entries, State),
commit_tree(State, New_tree@4, Reply@4);
{delete_multi, Keys, Reply@5} ->
New_tree@5 = fold_deletes(erlang:element(2, State), Keys, State),
commit_tree(State, New_tree@5, Reply@5);
{put_and_delete_multi, Puts, Deletes, Reply@6} ->
New_tree@6 = begin
_pipe = fold_inserts(erlang:element(2, State), Puts, State),
fold_deletes(_pipe, Deletes, State)
end,
commit_tree(State, New_tree@6, Reply@6);
{execute_transaction, Run, Reply@7} ->
Offset_before@1 = case trove@internal@store:current_offset(
erlang:element(3, State)
) of
{ok, Offset_before} -> Offset_before;
_assert_fail@4 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"handle_message"/utf8>>,
line => 331,
value => _assert_fail@4,
start => 8811,
'end' => 8882,
pattern_start => 8822,
pattern_end => 8839})
end,
Transaction = trove@internal@tx:new(
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State)
),
case Run(Transaction) of
{commit_outcome, New_tree@7} ->
commit_tree(State, New_tree@7, Reply@7);
cancel_outcome ->
Wasted = case trove@internal@store:current_offset(
erlang:element(3, State)
) of
{ok, Offset_after} ->
gleam@int:max(0, Offset_after - Offset_before@1);
{error, _} ->
0
end,
case Wasted > 0 of
true ->
Dirt_estimate = gleam@int:max(1, Wasted div 100),
New_tree@8 = trove@internal@btree:add_dirt(
erlang:element(2, State),
Dirt_estimate
),
case write_header(
erlang:element(3, State),
New_tree@8,
erlang:element(7, State)
) of
{ok, nil} -> nil;
_assert_fail@5 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"handle_message"/utf8>>,
line => 352,
value => _assert_fail@5,
start => 9647,
'end' => 9741,
pattern_start => 9658,
pattern_end => 9665})
end,
New_state = {db_state,
New_tree@8,
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State)},
gleam@otp@actor:send(Reply@7, nil),
gleam@otp@actor:continue(
maybe_auto_compact(New_state)
);
false ->
gleam@otp@actor:send(Reply@7, nil),
gleam@otp@actor:continue(State)
end
end;
{acquire_snapshot, Reply@8} ->
Snap_result = begin
_pipe@1 = trove@internal@store:open_reader(
erlang:element(3, State)
),
_pipe@2 = gleam@result:map(
_pipe@1,
fun(Reader) ->
trove@internal@snapshot:new(
erlang:element(2, State),
Reader,
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State)
)
end
),
gleam@result:map_error(
_pipe@2,
fun trove@internal@store:error_to_string/1
)
end,
gleam@otp@actor:send(Reply@8, Snap_result),
gleam@otp@actor:continue(State);
{compact, Reply@9} ->
case do_compact(State) of
{ok, Compacted} ->
gleam@otp@actor:send(Reply@9, {ok, nil}),
gleam@otp@actor:continue(Compacted);
{error, Reason} ->
gleam@otp@actor:send(
Reply@9,
{error, trove@internal@btree:error_to_string(Reason)}
),
gleam@otp@actor:continue(State)
end;
{size, Reply@10} ->
gleam@otp@actor:send(
Reply@10,
trove@internal@btree:size(erlang:element(2, State))
),
gleam@otp@actor:continue(State);
{dirt_factor, Reply@11} ->
gleam@otp@actor:send(
Reply@11,
compute_dirt_factor(erlang:element(2, State))
),
gleam@otp@actor:continue(State);
{sync_file, Reply@12} ->
case trove@internal@store:sync(erlang:element(3, State)) of
{ok, nil} -> nil;
_assert_fail@6 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"handle_message"/utf8>>,
line => 408,
value => _assert_fail@6,
start => 11130,
'end' => 11181,
pattern_start => 11141,
pattern_end => 11148})
end,
gleam@otp@actor:send(Reply@12, nil),
gleam@otp@actor:continue(State);
{set_auto_compact, Setting, Reply@13} ->
gleam@otp@actor:send(Reply@13, nil),
gleam@otp@actor:continue(
{db_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
Setting,
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State)}
);
{close, Reply@14} ->
case trove@internal@store:close(erlang:element(3, State)) of
{ok, nil} -> nil;
_assert_fail@7 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove/internal/db"/utf8>>,
function => <<"handle_message"/utf8>>,
line => 419,
value => _assert_fail@7,
start => 11412,
'end' => 11464,
pattern_start => 11423,
pattern_end => 11430})
end,
_ = trove_file_ffi:unlock(erlang:element(9, State)),
gleam@otp@actor:send(Reply@14, nil),
gleam@otp@actor:stop()
end.
-file("src/trove/internal/db.gleam", 445).
?DOC(false).
-spec recover_tree(trove@internal@store:store()) -> {ok,
trove@internal@btree:btree(any(), any())} |
{error, trove@internal@btree:btree_error()}.
recover_tree(Store) ->
gleam@result:'try'(
begin
_pipe = trove@internal@store:blank(Store),
gleam@result:map_error(
_pipe,
fun(Field@0) -> {store_error, Field@0} end
)
end,
fun(Is_blank) -> case Is_blank of
true ->
{ok, trove@internal@btree:new()};
false ->
gleam@result:'try'(
begin
_pipe@1 = trove@internal@store:get_latest_header(
Store
),
gleam@result:map_error(
_pipe@1,
fun(Field@0) -> {store_error, Field@0} end
)
end,
fun(Header) ->
trove@internal@btree:from_header(
erlang:element(2, Header),
erlang:element(3, Header),
erlang:element(4, Header),
32
)
end
)
end end
).
-file("src/trove/internal/db.gleam", 610).
?DOC(false).
-spec validate_tree_deep(trove@internal@store:store(), bitstring()) -> boolean().
validate_tree_deep(Store, Tree_data) ->
case trove@internal@btree@node:extract_locations(Tree_data) of
{error, _} ->
false;
{ok, Locations} ->
gleam@list:all(
Locations,
fun(Loc) -> case trove@internal@store:get_node(Store, Loc) of
{ok, Child_data} ->
case {trove@internal@btree@node:is_leaf(Tree_data),
trove@internal@btree@node:node_kind(Child_data)} of
{true, {ok, data_kind}} ->
true;
{false, {ok, tree_kind}} ->
trove@internal@btree@node:validate_structure(
Child_data
)
andalso validate_tree_deep(
Store,
Child_data
);
{_, _} ->
false
end;
{error, _} ->
false
end end
)
end.
-file("src/trove/internal/db.gleam", 587).
?DOC(false).
-spec validate_root_readable(
trove@internal@store:store(),
trove@internal@store:header()
) -> boolean().
validate_root_readable(Store, Header) ->
case trove@internal@btree:from_header(
erlang:element(2, Header),
erlang:element(3, Header),
erlang:element(4, Header),
32
) of
{error, _} ->
false;
{ok, _} ->
case erlang:element(2, Header) of
none ->
true;
{some, Offset} ->
case trove@internal@store:get_node(Store, Offset) of
{ok, Data} ->
trove@internal@btree@node:validate_structure(Data)
andalso validate_tree_deep(Store, Data);
{error, _} ->
false
end
end
end.
-file("src/trove/internal/db.gleam", 565).
?DOC(false).
-spec validate_store_file(binary(), boolean()) -> {ok, boolean()} |
{error, binary()}.
validate_store_file(File_path, Has_fallback) ->
case trove@internal@store:open(File_path) of
{error, Reason} ->
{error, trove@internal@store:error_to_string(Reason)};
{ok, Store} ->
Valid = case trove@internal@store:blank(Store) of
{ok, true} ->
not Has_fallback;
{ok, false} ->
case trove@internal@store:get_latest_header(Store) of
{ok, Header} ->
validate_root_readable(Store, Header);
{error, _} ->
false
end;
{error, _} ->
false
end,
_ = trove@internal@store:close(Store),
{ok, Valid}
end.
-file("src/trove/internal/db.gleam", 635).
?DOC(false).
-spec find_valid_store_number(binary(), list(integer())) -> {ok, integer()} |
{error, binary()}.
find_valid_store_number(Path, Candidates) ->
case Candidates of
[] ->
{ok, 0};
[N] ->
File_path = store_path(Path, N),
case validate_store_file(File_path, false) of
{ok, true} ->
{ok, N};
{ok, false} ->
{error,
<<<<"store file "/utf8, File_path/binary>>/binary,
" is corrupt and no valid fallback exists"/utf8>>};
{error, Reason} ->
{error,
<<<<<<"unable to validate store "/utf8,
File_path/binary>>/binary,
": "/utf8>>/binary,
Reason/binary>>}
end;
[N@1 | Rest] ->
File_path@1 = store_path(Path, N@1),
case validate_store_file(File_path@1, true) of
{ok, true} ->
Older@1 = case Rest of
[_ | Older] ->
Older;
[] ->
[]
end,
gleam@list:each(
Older@1,
fun(Old_n) ->
Old_path = store_path(Path, Old_n),
case trove_file_ffi:delete_file(Old_path) of
{ok, nil} ->
nil;
{error, Reason@1} ->
gleam_stdlib:println_error(
<<<<<<"[trove] failed to delete old store "/utf8,
Old_path/binary>>/binary,
": "/utf8>>/binary,
Reason@1/binary>>
)
end
end
),
{ok, N@1};
{ok, false} ->
_ = trove_file_ffi:delete_file(File_path@1),
find_valid_store_number(Path, Rest);
{error, Reason@2} ->
gleam_stdlib:println_error(
<<<<<<"[trove] unable to validate store "/utf8,
File_path@1/binary>>/binary,
", trying fallback: "/utf8>>/binary,
Reason@2/binary>>
),
_ = trove_file_ffi:delete_file(File_path@1),
find_valid_store_number(Path, Rest)
end
end.
-file("src/trove/internal/db.gleam", 557).
?DOC(false).
-spec find_highest_store_number(binary()) -> {ok, integer()} | {error, binary()}.
find_highest_store_number(Path) ->
gleam@result:'try'(
trove_file_ffi:list_dir(Path),
fun(Files) ->
Numbers = begin
_pipe = gleam@list:filter_map(Files, fun parse_trv_number/1),
gleam@list:sort(_pipe, fun(A, B) -> gleam@int:compare(B, A) end)
end,
find_valid_store_number(Path, Numbers)
end
).
-file("src/trove/internal/db.gleam", 698).
?DOC(false).
-spec get(gleam@erlang@process:subject(message(KET, KEU)), KET, integer()) -> gleam@option:option(KEU).
get(Subject, Key, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(_capture) -> {get, Key, _capture} end
).
-file("src/trove/internal/db.gleam", 706).
?DOC(false).
-spec put(gleam@erlang@process:subject(message(KEZ, KFA)), KEZ, KFA, integer()) -> nil.
put(Subject, Key, Value, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(_capture) -> {put, Key, Value, _capture} end
).
-file("src/trove/internal/db.gleam", 715).
?DOC(false).
-spec delete(gleam@erlang@process:subject(message(KFE, any())), KFE, integer()) -> nil.
delete(Subject, Key, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(_capture) -> {delete, Key, _capture} end
).
-file("src/trove/internal/db.gleam", 723).
?DOC(false).
-spec has_key(gleam@erlang@process:subject(message(KFJ, any())), KFJ, integer()) -> boolean().
has_key(Subject, Key, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(_capture) -> {has_key, Key, _capture} end
).
-file("src/trove/internal/db.gleam", 731).
?DOC(false).
-spec put_multi(
gleam@erlang@process:subject(message(KFO, KFP)),
list({KFO, KFP}),
integer()
) -> nil.
put_multi(Subject, Entries, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(_capture) -> {put_multi, Entries, _capture} end
).
-file("src/trove/internal/db.gleam", 739).
?DOC(false).
-spec delete_multi(
gleam@erlang@process:subject(message(KFU, any())),
list(KFU),
integer()
) -> nil.
delete_multi(Subject, Keys, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(_capture) -> {delete_multi, Keys, _capture} end
).
-file("src/trove/internal/db.gleam", 747).
?DOC(false).
-spec put_and_delete_multi(
gleam@erlang@process:subject(message(KGA, KGB)),
list({KGA, KGB}),
list(KGA),
integer()
) -> nil.
put_and_delete_multi(Subject, Puts, Deletes, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(_capture) -> {put_and_delete_multi, Puts, Deletes, _capture} end
).
-file("src/trove/internal/db.gleam", 760).
?DOC(false).
-spec transaction(
gleam@erlang@process:subject(message(KGH, KGI)),
integer(),
fun((trove@internal@tx:tx(KGH, KGI)) -> transaction_outcome(KGH, KGI))
) -> nil.
transaction(Subject, Timeout, Run) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(_capture) -> {execute_transaction, Run, _capture} end
).
-file("src/trove/internal/db.gleam", 768).
?DOC(false).
-spec acquire_snapshot(
gleam@erlang@process:subject(message(KGQ, KGR)),
integer()
) -> {ok, trove@internal@snapshot:snapshot(KGQ, KGR)} | {error, binary()}.
acquire_snapshot(Subject, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(Field@0) -> {acquire_snapshot, Field@0} end
).
-file("src/trove/internal/db.gleam", 775).
?DOC(false).
-spec compact(gleam@erlang@process:subject(message(any(), any())), integer()) -> {ok,
nil} |
{error, binary()}.
compact(Subject, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(Field@0) -> {compact, Field@0} end
).
-file("src/trove/internal/db.gleam", 782).
?DOC(false).
-spec size(gleam@erlang@process:subject(message(any(), any())), integer()) -> integer().
size(Subject, Timeout) ->
gleam@otp@actor:call(Subject, Timeout, fun(Field@0) -> {size, Field@0} end).
-file("src/trove/internal/db.gleam", 789).
?DOC(false).
-spec dirt_factor(
gleam@erlang@process:subject(message(any(), any())),
integer()
) -> float().
dirt_factor(Subject, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(Field@0) -> {dirt_factor, Field@0} end
).
-file("src/trove/internal/db.gleam", 796).
?DOC(false).
-spec file_sync(gleam@erlang@process:subject(message(any(), any())), integer()) -> nil.
file_sync(Subject, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(Field@0) -> {sync_file, Field@0} end
).
-file("src/trove/internal/db.gleam", 803).
?DOC(false).
-spec set_auto_compact(
gleam@erlang@process:subject(message(any(), any())),
auto_compact(),
integer()
) -> nil.
set_auto_compact(Subject, Setting, Timeout) ->
gleam@otp@actor:call(
Subject,
Timeout,
fun(_capture) -> {set_auto_compact, Setting, _capture} end
).
-file("src/trove/internal/db.gleam", 811).
?DOC(false).
-spec close(gleam@erlang@process:subject(message(any(), any())), integer()) -> nil.
close(Subject, Timeout) ->
gleam@otp@actor:call(Subject, Timeout, fun(Field@0) -> {close, Field@0} end).
-file("src/trove/internal/db.gleam", 468).
?DOC(false).
-spec open(
binary(),
trove@codec:codec(KED),
trove@codec:codec(KEF),
fun((KED, KED) -> gleam@order:order()),
file_sync(),
auto_compact(),
integer()
) -> {ok, gleam@erlang@process:subject(message(KED, KEF))} |
{error, internal_open_error()}.
open(
Path,
Key_codec,
Value_codec,
Key_compare,
Auto_file_sync,
Auto_compact,
Call_timeout
) ->
gleam@result:'try'(
begin
_pipe = trove_file_ffi:mkdir_p(Path),
gleam@result:map_error(
_pipe,
fun(Field@0) -> {internal_directory_error, Field@0} end
)
end,
fun(_) ->
Start_result = begin
_pipe@8 = gleam@otp@actor:new_with_initialiser(
Call_timeout,
fun(Subject) ->
gleam@result:'try'(
begin
_pipe@1 = trove_file_ffi:try_lock(Path),
gleam@result:map_error(
_pipe@1,
fun(Reason) ->
<<"lock:"/utf8, Reason/binary>>
end
)
end,
fun(_) ->
gleam@result:'try'(
begin
_pipe@2 = find_highest_store_number(
Path
),
gleam@result:map_error(
_pipe@2,
fun(Reason@1) ->
_ = trove_file_ffi:unlock(Path),
<<"failed to find store: "/utf8,
Reason@1/binary>>
end
)
end,
fun(File_number) ->
File_path = store_path(
Path,
File_number
),
gleam@result:'try'(
begin
_pipe@3 = trove@internal@store:open(
File_path
),
gleam@result:map_error(
_pipe@3,
fun(E) ->
_ = trove_file_ffi:unlock(
Path
),
<<"failed to open store: "/utf8,
(trove@internal@store:error_to_string(
E
))/binary>>
end
)
end,
fun(Store) ->
gleam@result:'try'(
begin
_pipe@4 = trove_file_ffi:dir_fsync(
Path
),
gleam@result:map_error(
_pipe@4,
fun(Reason@2) ->
_ = trove@internal@store:close(
Store
),
_ = trove_file_ffi:unlock(
Path
),
<<"failed to fsync directory: "/utf8,
Reason@2/binary>>
end
)
end,
fun(_) ->
gleam@result:'try'(
begin
_pipe@5 = recover_tree(
Store
),
gleam@result:map_error(
_pipe@5,
fun(E@1) ->
_ = trove@internal@store:close(
Store
),
_ = trove_file_ffi:unlock(
Path
),
<<"failed to recover tree: "/utf8,
(trove@internal@btree:error_to_string(
E@1
))/binary>>
end
)
end,
fun(Tree) ->
State = {db_state,
Tree,
Store,
Key_codec,
Value_codec,
Key_compare,
Auto_file_sync,
Auto_compact,
Path,
File_number,
Call_timeout},
_pipe@6 = gleam@otp@actor:initialised(
State
),
_pipe@7 = gleam@otp@actor:returning(
_pipe@6,
Subject
),
{ok, _pipe@7}
end
)
end
)
end
)
end
)
end
)
end
),
_pipe@9 = gleam@otp@actor:on_message(
_pipe@8,
fun handle_message/2
),
gleam@otp@actor:start(_pipe@9)
end,
case Start_result of
{ok, Started} ->
{ok, erlang:element(3, Started)};
{error, {init_failed, Reason@3}} ->
case gleam_stdlib:string_starts_with(
Reason@3,
<<"lock:"/utf8>>
) of
true ->
{error,
{internal_lock_error,
gleam@string:drop_start(
Reason@3,
string:length(<<"lock:"/utf8>>)
)}};
false ->
{error, {internal_store_error, Reason@3}}
end;
{error, init_timeout} ->
{error, internal_actor_start_error};
{error, {init_exited, _}} ->
{error, internal_actor_start_error}
end
end
).