Current section

Files

Jump to
database src database.erl
Raw

src/database.erl

-module(database).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src\\database.gleam").
-export([transaction/2, insert/2, delete/2, find/2, drop_table/1, create_table/2]).
-export_type([storage/0, table_attributes/0, table_ref/1, table/1, table_error/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(
" An outrageously simple set of functions to interact with the BEAM\n"
" DETS (Disk-based Erlang Term Storage) API.\n"
" \n"
" Good for small projects and POCs.\n"
"\n"
"\n"
" This project DOES NOT intend to serve as direct bindings to the \n"
" DETS API, but rather to interact with it in a gleamy way:\n"
" 1. with a simple and concise interface;\n"
" 2. type-safely;\n"
" 3. no unexpected crashes, all errors are values.\n"
).
-type storage() :: set.
-type table_attributes() :: {file, gleam@erlang@charlist:charlist()} |
{type, storage()} |
{keypos, integer()}.
-type table_ref(EKR) :: any() | {gleam_phantom, EKR}.
-opaque table(EKS) :: {table,
gleam@erlang@atom:atom_(),
list(table_attributes()),
gleam@erlang@charlist:charlist()} |
{gleam_phantom, EKS}.
-type table_error() :: badarg |
index_out_of_bounds |
unable_to_open |
unable_to_close.
-file("src\\database.gleam", 157).
?DOC(" Ensures type-safety cryptographically\n").
-spec generate_signature(any()) -> binary().
generate_signature(Value) ->
_pipe = <<(gleam@string:inspect(Value))/binary>>,
_pipe@1 = gleam@crypto:hash(sha256, _pipe),
gleam@bit_array:base64_url_encode(_pipe@1, false).
-file("src\\database.gleam", 181).
?DOC(
" Allows you to interact with the table.\n"
"\n"
" It opens and locks the .dets file, then execute your operations.\n"
" Once the operations are done, it writes the changes into the file,\n"
" closes and releases it.\n"
"\n"
" # Example\n"
"\n"
" ```gleam\n"
" pub fn is_pet_registered(table: Table(Pet), petname: String) {\n"
" use ref <- database.transaction(table)\n"
" case database.find(ref, petname) {\n"
" Some(_) -> True\n"
" None -> False\n"
" }\n"
" }\n"
" ```\n"
).
-spec transaction(table(EMF), fun((table_ref(EMF)) -> EMI)) -> {ok, EMI} |
{error, table_error()}.
transaction(Table, Procedure) ->
case dets:open_file(erlang:element(2, Table), erlang:element(3, Table)) of
{error, _} ->
{error, unable_to_open};
{ok, Ref} ->
Resp = Procedure(Ref),
case dets:close(Ref) of
{error, _} ->
{error, unable_to_close};
_ ->
{ok, Resp}
end
end.
-file("src\\database.gleam", 218).
?DOC(
" Inserts a value into a table.\n"
"\n"
" DETS tables do not have support for update, only for upsert.\n"
" So if you have to change a value, just insert a new value\n"
" with the same index, and it will replace the previous value.\n"
"\n"
" # Example\n"
"\n"
" ```gleam\n"
" pub fn new_pet(table: Table(Pet), animal: Animal, name: String) {\n"
" let pet = Pet(name, animal)\n"
" let op = database.transaction(table, fn(ref) {\n"
" database.insert(ref, pet)\n"
" })\n"
" case op {\n"
" Ok(_) -> Ok(pet)\n"
" Error(reason) -> Error(reason)\n"
" }\n"
" }\n"
" ```\n"
).
-spec insert(table_ref(EML), EML) -> {ok, nil} | {error, any()}.
insert(Transac, Value) ->
case dets:insert(Transac, Value) of
{error, Reason} ->
{error, Reason};
_ ->
{ok, nil}
end.
-file("src\\database.gleam", 236).
?DOC(
" Deletes a value from a table.\n"
"\n"
" # Example\n"
"\n"
" ```gleam\n"
" pub fn delete_pet(table: Table(Pet) petname: String) {\n"
" use ref <- database.transaction(table)\n"
" database.delete(ref, petname)\n"
" }\n"
" ```\n"
).
-spec delete(table_ref(any()), any()) -> {ok, nil} | {error, any()}.
delete(Transac, Index) ->
case dets:delete(Transac, Index) of
{error, Reason} ->
{error, Reason};
_ ->
{ok, nil}
end.
-file("src\\database.gleam", 258).
?DOC(
" Finds a value by its index\n"
"\n"
" # Example\n"
"\n"
" ```gleam\n"
" pub fn play_with_pluto(table: Table(Pet)) {\n"
" use ref <- database.transaction(table)\n"
" let resp = database.find(ref, \"Pluto\")\n"
" case resp {\n"
" None -> Error(PlutoNotFoundBlameTheAstronomers)\n"
" Some(pluto) -> Ok(play_with(pluto))\n"
" }\n"
" }\n"
" ```\n"
).
-spec find(table_ref(EMS), any()) -> gleam@option:option(EMS).
find(Transac, Index) ->
case dets:lookup(Transac, Index) of
[Resp] ->
{some, Resp};
_ ->
none
end.
-file("src\\database.gleam", 281).
?DOC(
" Deletes the entire table file\n"
"\n"
" # Example\n"
"\n"
" ```gleam\n"
" pub fn destroy_all_pets(table: Table(Pet), password: String) {\n"
" case password {\n"
" \"Yes, I am evil.\" -> {\n"
" database.drop_table(table)\n"
" Ok(Nil)\n"
" }\n"
" _ -> Error(WrongPassword)\n"
" }\n"
" }\n"
" ```\n"
).
-spec drop_table(table(any())) -> {ok, nil} | {error, any()}.
drop_table(Table) ->
case file:delete(erlang:element(4, Table)) of
{error, Reason} ->
{error, Reason};
_ ->
{ok, nil}
end.
-file("src\\database.gleam", 288).
-spec is_record(any()) -> boolean().
is_record(Value) ->
erlang:is_tuple(Value) andalso erlang:is_atom(erlang:element(1, Value)).
-file("src\\database.gleam", 119).
?DOC(
" Creats a table.\n"
"\n"
" ## Important\n"
" **THE TABLE IS ENTIRELY DEPENDENT ON THE DEFINITION PROVIDED,\n"
" ANY CHANGE TO DEFINITION - EVEN IF EVERYTHING REMAINS OF THE SAME\n"
" TYPE - WILL CAUSE ANOTHER TABLE TO BE CREATED.**\n"
"\n"
" If no .dets file exists for the provided definition, creates one.\n"
" Otherwise, just checks whether the file is accessible and not\n"
" corrupted.\n"
"\n"
" # Example\n"
"\n"
" ```gleam\n"
" pub fn start_database() {\n"
" let pluto = Pet(name: \"Pluto\", animal: Dog)\n"
" database.create_table(definition: pluto, index_at: 0)\n"
" // -> Ok(Table(Pet))\n"
" }\n"
" ```\n"
).
-spec create_table(ELZ, integer()) -> {ok, table(ELZ)} | {error, table_error()}.
create_table(Definition, Keypos) ->
case {is_record(Definition), Keypos >= 0} of
{true, true} ->
case (Keypos + 2) > erlang:tuple_size(Definition) of
true ->
{error, index_out_of_bounds};
false ->
Original_atom = erlang:element(1, Definition),
Name = <<<<(erlang:atom_to_binary(Original_atom))/binary,
"_"/utf8>>/binary,
(generate_signature(Definition))/binary>>,
New_atom = erlang:binary_to_atom(Name),
Path = unicode:characters_to_list(
<<Name/binary, ".dets"/utf8>>
),
Att = [{file, Path}, {type, set}, {keypos, Keypos + 2}],
case dets:open_file(New_atom, Att) of
{ok, Tab} ->
case dets:close(Tab) of
{error, _} ->
{error, unable_to_close};
_ ->
{ok, {table, New_atom, Att, Path}}
end;
{error, _} ->
{error, unable_to_open}
end
end;
{false, _} ->
{error, badarg};
{_, false} ->
{error, index_out_of_bounds}
end.