Packages

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

Current section

Files

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

src/trove@internal@store.erl

-module(trove@internal@store).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/trove/internal/store.gleam").
-export([error_to_string/1, open/1, open_reader/1, close/1, sync/1, current_offset/1, blank/1, put_node/2, put_header/2, get_node/2, get_latest_header/1]).
-export_type([store_error/0, store/0, header/0]).
-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 store_error() :: {io_error, binary()} |
checksum_mismatch |
invalid_node_header |
node_exceeds_bounds |
no_header_found.
-opaque store() :: {store,
trove@internal@store@file_ffi:file_handle(),
binary()}.
-type header() :: {header, gleam@option:option(integer()), integer(), integer()}.
-file("src/trove/internal/store.gleam", 17).
?DOC(false).
-spec error_to_string(store_error()) -> binary().
error_to_string(Error) ->
case Error of
{io_error, Detail} ->
<<"I/O error: "/utf8, Detail/binary>>;
checksum_mismatch ->
<<"checksum mismatch"/utf8>>;
invalid_node_header ->
<<"invalid node header"/utf8>>;
node_exceeds_bounds ->
<<"node size exceeds file bounds"/utf8>>;
no_header_found ->
<<"no header found"/utf8>>
end.
-file("src/trove/internal/store.gleam", 35).
?DOC(false).
-spec map_io({ok, HKV} | {error, binary()}) -> {ok, HKV} |
{error, store_error()}.
map_io(Result) ->
gleam@result:map_error(Result, fun(S) -> {io_error, S} end).
-file("src/trove/internal/store.gleam", 50).
?DOC(false).
-spec open(binary()) -> {ok, store()} | {error, store_error()}.
open(Path) ->
_pipe = trove_file_ffi:open(Path),
_pipe@1 = gleam@result:map(
_pipe,
fun(_capture) -> {store, _capture, Path} end
),
map_io(_pipe@1).
-file("src/trove/internal/store.gleam", 57).
?DOC(false).
-spec open_reader(store()) -> {ok, store()} | {error, store_error()}.
open_reader(Store) ->
_pipe = trove_file_ffi:open_read(erlang:element(3, Store)),
_pipe@1 = gleam@result:map(
_pipe,
fun(_capture) -> {store, _capture, erlang:element(3, Store)} end
),
map_io(_pipe@1).
-file("src/trove/internal/store.gleam", 64).
?DOC(false).
-spec close(store()) -> {ok, nil} | {error, store_error()}.
close(Store) ->
_pipe = trove_file_ffi:close(erlang:element(2, Store)),
map_io(_pipe).
-file("src/trove/internal/store.gleam", 225).
?DOC(false).
-spec sync(store()) -> {ok, nil} | {error, store_error()}.
sync(Store) ->
_pipe = trove_file_ffi:datasync(erlang:element(2, Store)),
map_io(_pipe).
-file("src/trove/internal/store.gleam", 231).
?DOC(false).
-spec current_offset(store()) -> {ok, integer()} | {error, store_error()}.
current_offset(Store) ->
_pipe = trove_file_ffi:file_size(erlang:element(2, Store)),
map_io(_pipe).
-file("src/trove/internal/store.gleam", 237).
?DOC(false).
-spec blank(store()) -> {ok, boolean()} | {error, store_error()}.
blank(Store) ->
_pipe = trove_file_ffi:file_size(erlang:element(2, Store)),
_pipe@1 = gleam@result:map(_pipe, fun(Size) -> Size =:= 0 end),
map_io(_pipe@1).
-file("src/trove/internal/store.gleam", 149).
?DOC(false).
-spec pad_to_block_boundary(
trove@internal@store@file_ffi:file_handle(),
integer()
) -> {ok, integer()} | {error, store_error()}.
pad_to_block_boundary(Handle, Current_size) ->
Remainder = case 1024 of
0 -> 0;
Gleam@denominator -> Current_size rem Gleam@denominator
end,
case Remainder of
0 ->
{ok, 0};
_ ->
Padding_size = 1024 - Remainder,
Padding = <<0:(lists:max([(Padding_size), 0]))/unit:8>>,
_pipe = trove_file_ffi:append(Handle, Padding),
map_io(_pipe)
end.
-file("src/trove/internal/store.gleam", 71).
?DOC(false).
-spec put_node(store(), bitstring()) -> {ok, integer()} | {error, store_error()}.
put_node(Store, Data) ->
Size = erlang:byte_size(Data),
Checksum = trove_file_ffi:hash(Data),
Payload = <<16#00:8, Size:32, Data/bitstring, Checksum/bitstring>>,
_pipe = trove_file_ffi:append(erlang:element(2, Store), Payload),
map_io(_pipe).
-file("src/trove/internal/store.gleam", 125).
?DOC(false).
-spec put_header(store(), header()) -> {ok, integer()} | {error, store_error()}.
put_header(Store, Header) ->
gleam@result:'try'(
begin
_pipe = trove_file_ffi:file_size(erlang:element(2, Store)),
map_io(_pipe)
end,
fun(Current_size) ->
gleam@result:'try'(
pad_to_block_boundary(erlang:element(2, Store), Current_size),
fun(_) ->
{Has_root, Root_val} = case erlang:element(2, Header) of
{some, R} ->
{1, R};
none ->
{0, 0}
end,
Fields = <<Has_root:8,
Root_val:64/integer-big,
(erlang:element(3, Header)):64/integer-big,
(erlang:element(4, Header)):64/integer-big>>,
Checksum = trove_file_ffi:hash(Fields),
Payload = <<16#2A:8, Fields/bitstring, Checksum/bitstring>>,
_pipe@1 = trove_file_ffi:append(
erlang:element(2, Store),
Payload
),
map_io(_pipe@1)
end
)
end
).
-file("src/trove/internal/store.gleam", 83).
?DOC(false).
-spec get_node(store(), integer()) -> {ok, bitstring()} | {error, store_error()}.
get_node(Store, Location) ->
gleam@result:'try'(
begin
_pipe = trove_file_ffi:pread(erlang:element(2, Store), Location, 5),
map_io(_pipe)
end,
fun(Prefix) -> case Prefix of
<<16#00, Size:32>> ->
gleam@result:'try'(
begin
_pipe@1 = trove_file_ffi:file_size(
erlang:element(2, Store)
),
map_io(_pipe@1)
end,
fun(Current_size) ->
Required = ((Location + 5) + Size) + 16,
gleam@bool:guard(
Required > Current_size,
{error, node_exceeds_bounds},
fun() ->
gleam@result:'try'(
begin
_pipe@2 = trove_file_ffi:pread(
erlang:element(2, Store),
Location + 5,
Size
),
map_io(_pipe@2)
end,
fun(Data) ->
gleam@result:'try'(
begin
_pipe@3 = trove_file_ffi:pread(
erlang:element(2, Store),
(Location + 5) + Size,
16
),
map_io(_pipe@3)
end,
fun(Stored_checksum) ->
Computed = trove_file_ffi:hash(
Data
),
case Computed =:= Stored_checksum of
true ->
{ok, Data};
false ->
{error,
checksum_mismatch}
end
end
)
end
)
end
)
end
);
_ ->
{error, invalid_node_header}
end end
).
-file("src/trove/internal/store.gleam", 191).
?DOC(false).
-spec try_parse_header(store(), integer()) -> {ok, header()} | {error, nil}.
try_parse_header(Store, Offset) ->
gleam@result:'try'(
begin
_pipe = trove_file_ffi:pread(erlang:element(2, Store), Offset, 1),
gleam@result:replace_error(_pipe, nil)
end,
fun(Marker_bytes) ->
gleam@bool:guard(
Marker_bytes /= <<16#2A>>,
{error, nil},
fun() ->
gleam@result:'try'(
begin
_pipe@1 = trove_file_ffi:pread(
erlang:element(2, Store),
Offset,
42
),
gleam@result:replace_error(_pipe@1, nil)
end,
fun(Raw) -> case Raw of
<<_:8,
Fields:25/binary,
Stored_checksum:16/binary>> ->
Computed = trove_file_ffi:hash(Fields),
gleam@bool:guard(
Computed /= Stored_checksum,
{error, nil},
fun() -> case Fields of
<<Has_root:8,
Root_val:64/integer-big,
Size:64/integer-big,
Dirt:64/integer-big>> ->
case Has_root of
1 ->
{ok,
{header,
{some,
Root_val},
Size,
Dirt}};
0 ->
{ok,
{header,
none,
Size,
Dirt}};
_ ->
{error, nil}
end;
_ ->
{error, nil}
end end
);
_ ->
{error, nil}
end end
)
end
)
end
).
-file("src/trove/internal/store.gleam", 183).
?DOC(false).
-spec scan_for_header(store(), integer()) -> {ok, header()} |
{error, store_error()}.
scan_for_header(Store, Offset) ->
gleam@bool:guard(
Offset < 0,
{error, no_header_found},
fun() -> case try_parse_header(Store, Offset) of
{ok, Header} ->
{ok, Header};
{error, nil} ->
scan_for_header(Store, Offset - 1024)
end end
).
-file("src/trove/internal/store.gleam", 167).
?DOC(false).
-spec get_latest_header(store()) -> {ok, header()} | {error, store_error()}.
get_latest_header(Store) ->
gleam@result:'try'(
begin
_pipe = trove_file_ffi:file_size(erlang:element(2, Store)),
map_io(_pipe)
end,
fun(File_size) -> case File_size of
0 ->
{error, no_header_found};
_ ->
Last_block_offset = (case 1024 of
0 -> 0;
Gleam@denominator -> (File_size - 1) div Gleam@denominator
end) * 1024,
scan_for_header(Store, Last_block_offset)
end end
).