Current section
Files
Jump to
Current section
Files
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, get_node/2, encoded_size/1, get_latest_header/1, put_header/2]).
-export_type([error/0, store/0, keyspace_header/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 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 keyspace_header() :: {keyspace_header,
binary(),
gleam@option:option(integer()),
integer(),
integer()}.
-type header() :: {header,
gleam@option:option(integer()),
integer(),
integer(),
list(keyspace_header())}.
-file("src/trove/internal/store.gleam", 18).
?DOC(false).
-spec error_to_string(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", 44).
?DOC(false).
-spec map_io({ok, HKN} | {error, binary()}) -> {ok, HKN} | {error, error()}.
map_io(Result) ->
gleam@result:map_error(Result, fun(S) -> {io_error, S} end).
-file("src/trove/internal/store.gleam", 77).
?DOC(false).
-spec open(binary()) -> {ok, store()} | {error, 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", 84).
?DOC(false).
-spec open_reader(store()) -> {ok, store()} | {error, 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", 91).
?DOC(false).
-spec close(store()) -> {ok, nil} | {error, error()}.
close(Store) ->
_pipe = trove_file_ffi:close(erlang:element(2, Store)),
map_io(_pipe).
-file("src/trove/internal/store.gleam", 187).
?DOC(false).
-spec default_bytes(gleam@option:option(integer()), integer(), integer()) -> bitstring().
default_bytes(Root, Size, Dirt) ->
{Has_root, Root_val} = case Root of
{some, R} ->
{1, R};
none ->
{0, 0}
end,
<<Has_root:8,
Root_val:64/integer-big,
Size:64/integer-big,
Dirt:64/integer-big>>.
-file("src/trove/internal/store.gleam", 200).
?DOC(false).
-spec keyspace_bytes(keyspace_header()) -> bitstring().
keyspace_bytes(Ks) ->
Name_bytes = gleam_stdlib:identity(erlang:element(2, Ks)),
Name_len = erlang:byte_size(Name_bytes),
{Has_root, Root_val} = case erlang:element(3, Ks) of
{some, R} ->
{1, R};
none ->
{0, 0}
end,
<<Name_len:16/integer-big,
Name_bytes/bitstring,
Has_root:8,
Root_val:64/integer-big,
(erlang:element(4, Ks)):64/integer-big,
(erlang:element(5, Ks)):64/integer-big>>.
-file("src/trove/internal/store.gleam", 177).
?DOC(false).
-spec payload_bytes(header()) -> bitstring().
payload_bytes(Header) ->
Default = default_bytes(
erlang:element(2, Header),
erlang:element(3, Header),
erlang:element(4, Header)
),
Count = erlang:length(erlang:element(5, Header)),
Keyspaces = gleam@list:fold(
erlang:element(5, Header),
<<>>,
fun(Acc, Ks) -> <<Acc/bitstring, (keyspace_bytes(Ks))/bitstring>> end
),
<<Default/bitstring, Count:16/integer-big, Keyspaces/bitstring>>.
-file("src/trove/internal/store.gleam", 328).
?DOC(false).
-spec decode_payload_length(bitstring()) -> {ok, integer()} | {error, nil}.
decode_payload_length(Bytes) ->
case Bytes of
<<N:32/integer-big>> ->
{ok, N};
_ ->
{error, nil}
end.
-file("src/trove/internal/store.gleam", 352).
?DOC(false).
-spec decode_root_flag(integer(), integer()) -> {ok,
gleam@option:option(integer())} |
{error, nil}.
decode_root_flag(Has_root, Root_val) ->
case Has_root of
1 ->
{ok, {some, Root_val}};
0 ->
{ok, none};
_ ->
{error, nil}
end.
-file("src/trove/internal/store.gleam", 335).
?DOC(false).
-spec decode_default_fields(bitstring()) -> {ok,
{gleam@option:option(integer()), integer(), integer()}} |
{error, nil}.
decode_default_fields(Fields) ->
case Fields of
<<Has_root:8,
Root_val:64/integer-big,
Size:64/integer-big,
Dirt:64/integer-big>> ->
gleam@result:'try'(
decode_root_flag(Has_root, Root_val),
fun(Root) -> {ok, {Root, Size, Dirt}} end
);
_ ->
{error, nil}
end.
-file("src/trove/internal/store.gleam", 383).
?DOC(false).
-spec decode_one_keyspace(bitstring()) -> {ok, {keyspace_header(), bitstring()}} |
{error, nil}.
decode_one_keyspace(Bits) ->
case Bits of
<<Name_len:16/integer-big,
Name_bytes:Name_len/binary,
Has_root:8,
Root_val:64/integer-big,
Size:64/integer-big,
Dirt:64/integer-big,
Tail/bitstring>> ->
gleam@result:'try'(
begin
_pipe = gleam@bit_array:to_string(Name_bytes),
gleam@result:replace_error(_pipe, nil)
end,
fun(Name) ->
gleam@result:'try'(
decode_root_flag(Has_root, Root_val),
fun(Root) ->
{ok,
{{keyspace_header, Name, Root, Size, Dirt},
Tail}}
end
)
end
);
_ ->
{error, nil}
end.
-file("src/trove/internal/store.gleam", 363).
?DOC(false).
-spec decode_keyspaces(bitstring(), integer(), list(keyspace_header())) -> {ok,
list(keyspace_header())} |
{error, nil}.
decode_keyspaces(Bits, Remaining, Acc) ->
case Remaining of
0 ->
gleam@bool:guard(
erlang:byte_size(Bits) /= 0,
{error, nil},
fun() -> {ok, lists:reverse(Acc)} end
);
_ ->
gleam@result:'try'(
decode_one_keyspace(Bits),
fun(_use0) ->
{Entry, Tail} = _use0,
decode_keyspaces(Tail, Remaining - 1, [Entry | Acc])
end
)
end.
-file("src/trove/internal/store.gleam", 311).
?DOC(false).
-spec decode_payload(bitstring()) -> {ok, header()} | {error, nil}.
decode_payload(Payload) ->
case Payload of
<<Default_fields:25/binary,
Count:16/integer-big,
Keyspaces_bits/bitstring>> ->
gleam@result:'try'(
decode_default_fields(Default_fields),
fun(_use0) ->
{Root, Size, Dirt} = _use0,
gleam@result:'try'(
decode_keyspaces(Keyspaces_bits, Count, []),
fun(Keyspaces) ->
{ok, {header, Root, Size, Dirt, Keyspaces}}
end
)
end
);
_ ->
{error, nil}
end.
-file("src/trove/internal/store.gleam", 427).
?DOC(false).
-spec sync(store()) -> {ok, nil} | {error, error()}.
sync(Store) ->
_pipe = trove_file_ffi:datasync(erlang:element(2, Store)),
map_io(_pipe).
-file("src/trove/internal/store.gleam", 433).
?DOC(false).
-spec current_offset(store()) -> {ok, integer()} | {error, error()}.
current_offset(Store) ->
_pipe = trove_file_ffi:file_size(erlang:element(2, Store)),
map_io(_pipe).
-file("src/trove/internal/store.gleam", 439).
?DOC(false).
-spec blank(store()) -> {ok, boolean()} | {error, 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", 217).
?DOC(false).
-spec pad_to_block_boundary(
trove@internal@store@file_ffi:file_handle(),
integer()
) -> {ok, integer()} | {error, 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", 98).
?DOC(false).
-spec put_node(store(), bitstring()) -> {ok, integer()} | {error, 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", 107).
?DOC(false).
-spec get_node(store(), integer()) -> {ok, bitstring()} | {error, 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", 406).
?DOC(false).
-spec parse_legacy_header(store(), integer()) -> {ok, header()} | {error, nil}.
parse_legacy_header(Store, Offset) ->
gleam@result:'try'(
begin
_pipe = trove_file_ffi:pread(erlang:element(2, Store), Offset, 42),
gleam@result:replace_error(_pipe, 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() ->
gleam@result:'try'(
decode_default_fields(Fields),
fun(_use0) ->
{Root, Size, Dirt} = _use0,
{ok, {header, Root, Size, Dirt, []}}
end
)
end
);
_ ->
{error, nil}
end end
).
-file("src/trove/internal/store.gleam", 71).
?DOC(false).
-spec encoded_size(header()) -> integer().
encoded_size(Header) ->
Payload_size = begin
_pipe = payload_bytes(Header),
erlang:byte_size(_pipe)
end,
((1 + 4) + Payload_size) + 16.
-file("src/trove/internal/store.gleam", 271).
?DOC(false).
-spec parse_v2_header(store(), integer()) -> {ok, header()} | {error, nil}.
parse_v2_header(Store, Offset) ->
gleam@result:'try'(
begin
_pipe = trove_file_ffi:pread(
erlang:element(2, Store),
Offset + 1,
4
),
gleam@result:replace_error(_pipe, nil)
end,
fun(Len_bytes) ->
gleam@result:'try'(
decode_payload_length(Len_bytes),
fun(Payload_size) ->
gleam@result:'try'(
begin
_pipe@1 = trove_file_ffi:file_size(
erlang:element(2, Store)
),
gleam@result:replace_error(_pipe@1, nil)
end,
fun(File_size) ->
Record_end = (((Offset + 1) + 4) + Payload_size) + 16,
gleam@bool:guard(
(Payload_size < 0) orelse (Record_end > File_size),
{error, nil},
fun() ->
gleam@result:'try'(
begin
_pipe@2 = trove_file_ffi:pread(
erlang:element(2, Store),
(Offset + 1) + 4,
Payload_size
),
gleam@result:replace_error(
_pipe@2,
nil
)
end,
fun(Payload) ->
gleam@result:'try'(
begin
_pipe@3 = trove_file_ffi:pread(
erlang:element(2, Store),
((Offset + 1) + 4) + Payload_size,
16
),
gleam@result:replace_error(
_pipe@3,
nil
)
end,
fun(Stored_checksum) ->
Computed = trove_file_ffi:hash(
Payload
),
gleam@bool:guard(
Computed /= Stored_checksum,
{error, nil},
fun() ->
decode_payload(
Payload
)
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/trove/internal/store.gleam", 258).
?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) -> case Marker_bytes of
<<Marker>> when Marker =:= 16#2B ->
parse_v2_header(Store, Offset);
<<Marker@1>> when Marker@1 =:= 16#2A ->
parse_legacy_header(Store, Offset);
_ ->
{error, nil}
end end
).
-file("src/trove/internal/store.gleam", 250).
?DOC(false).
-spec scan_for_header(store(), integer()) -> {ok, header()} | {error, 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", 237).
?DOC(false).
-spec get_latest_header(store()) -> {ok, header()} | {error, 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
).
-file("src/trove/internal/store.gleam", 155).
?DOC(false).
-spec put_header(store(), header()) -> {ok, integer()} | {error, error()}.
put_header(Store, Header) ->
case erlang:length(erlang:element(5, Header)) =< 65535 of
true -> 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/store"/utf8>>,
function => <<"put_header"/utf8>>,
line => 159,
value => _assert_fail,
start => 4523,
'end' => 4587,
pattern_start => 4534,
pattern_end => 4538})
end,
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(_) ->
Payload = payload_bytes(Header),
Payload_size = erlang:byte_size(Payload),
Checksum = trove_file_ffi:hash(Payload),
Record = <<16#2B:8,
Payload_size:32/integer-big,
Payload/bitstring,
Checksum/bitstring>>,
_pipe@1 = trove_file_ffi:append(
erlang:element(2, Store),
Record
),
map_io(_pipe@1)
end
)
end
).