Current section
Files
Jump to
Current section
Files
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, select/2, field/3]).
-export_type([storage/0, table_attributes/0, table_ref/1, transaction/1, table/1, table_error/0, select_option/1]).
-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(EIC) :: any() | {gleam_phantom, EIC}.
-opaque transaction(EID) :: {transaction,
table_ref(EID),
gleam@dynamic@decode:decoder(EID)}.
-opaque table(EIE) :: {table,
gleam@erlang@atom:atom_(),
list(table_attributes()),
gleam@erlang@charlist:charlist(),
gleam@dynamic@decode:decoder(EIE)}.
-type table_error() :: badarg | unable_to_open | unable_to_close.
-type select_option(EIF) :: skip | {continue, EIF} | {done, EIF}.
-file("src\\database.gleam", 183).
?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(EJY), fun((transaction(EJY)) -> EKB)) -> {ok, EKB} |
{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({transaction, Ref, erlang:element(5, Table)}),
case dets:close(Ref) of
{error, _} ->
{error, unable_to_close};
_ ->
{ok, Resp}
end
end.
-file("src\\database.gleam", 220).
?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(transaction(EKE), EKE) -> {ok, nil} | {error, any()}.
insert(Transac, Value) ->
case dets:insert(erlang:element(2, Transac), Value) of
{error, Reason} ->
{error, Reason};
_ ->
{ok, nil}
end.
-file("src\\database.gleam", 238).
?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(transaction(any()), any()) -> {ok, nil} | {error, any()}.
delete(Transac, Index) ->
case dets:delete(erlang:element(2, Transac), Index) of
{error, Reason} ->
{error, Reason};
_ ->
{ok, nil}
end.
-file("src\\database.gleam", 260).
?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(transaction(EKL), any()) -> gleam@option:option(EKL).
find(Transac, Index) ->
case dets:lookup(erlang:element(2, Transac), Index) of
[Resp] ->
case gleam@dynamic@decode:run(Resp, erlang:element(3, Transac)) of
{ok, Val} ->
{some, Val};
_ ->
none
end;
_ ->
none
end.
-file("src\\database.gleam", 287).
?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", 294).
-spec is_record(any()) -> boolean().
is_record(Value) ->
erlang:is_tuple(Value) andalso erlang:is_atom(erlang:element(1, Value)).
-file("src\\database.gleam", 133).
?DOC(
" Creats a table.\n"
"\n"
" ## Important\n"
" **The table created will have the same name as definition provided.**\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 table_def = Pet(name: \"Pluto\", animal: Dog)\n"
" let decoder = {\n"
" use name <- database.field(0, decode.string)\n"
" use animal <- database.field(1, my_animal_decoder)\n"
" decode.success(Pet(name:, animal:))\n"
" }\n"
" database.create_table(definition: table_def, decode_with: decoder)\n"
" // -> Ok(Table(Pet))\n"
" }\n"
" ```\n"
).
-spec create_table(EJT, gleam@dynamic@decode:decoder(EJT)) -> {ok, table(EJT)} |
{error, table_error()}.
create_table(Definition, Decoder) ->
case is_record(Definition) of
true ->
case erlang:tuple_size(Definition) < 2 of
true ->
{error, badarg};
false ->
Name_atom = erlang:element(1, Definition),
Name = erlang:atom_to_binary(Name_atom),
Path = unicode:characters_to_list(
<<Name/binary, ".dets"/utf8>>
),
Att = [{file, Path}, {type, set}, {keypos, 2}],
case dets:open_file(Name_atom, Att) of
{ok, Tab} ->
case dets:close(Tab) of
{error, _} ->
{error, unable_to_close};
_ ->
{ok, {table, Name_atom, Att, Path, Decoder}}
end;
{error, _} ->
{error, unable_to_open}
end
end;
false ->
{error, badarg}
end.
-file("src\\database.gleam", 332).
?DOC(
" Searches for somethig on the table.\n"
"\n"
" # Example\n"
" \n"
" ```gleam\n"
" pub fn fetch_all_parrots(table: Table(Pet)) {\n"
" use ref <- database.transaction(table)\n"
" use value <- database.select(ref)\n"
" case value {\n"
" Pet(_name, Parrot) -> Continue(value)\n"
" _ -> Skip\n"
" }\n"
" }\n"
" ```\n"
"\n"
" # IMPORTANT\n"
" **DETS tables are not sorted in any deterministic way, so\n"
" never assume that the last value inserted will be the last\n"
" one on the table.**\n"
).
-spec select(transaction(EKU), fun((EKU) -> select_option(EKW))) -> list(EKW).
select(Transac, Select_fn) ->
Continue = erlang:binary_to_atom(<<"continue"/utf8>>),
New_fn = fun(Dyn_value) ->
case gleam@dynamic@decode:run(Dyn_value, erlang:element(3, Transac)) of
{ok, Value} ->
case Select_fn(Value) of
skip ->
Continue;
Res ->
Res
end;
_ ->
Continue
end
end,
dets:traverse(erlang:element(2, Transac), New_fn).
-file("src\\database.gleam", 362).
?DOC(
" Field decoder\n"
" \n"
" # Example\n"
" \n"
" ```gleam\n"
" let decoder = {\n"
" use name <- database.field(0, decode.string)\n"
" use animal <- database.field(1, my_animal_decoder)\n"
" decode.success(Pet(name:, animal:))\n"
" }\n"
" ```\n"
).
-spec field(
integer(),
gleam@dynamic@decode:decoder(EKZ),
fun((EKZ) -> gleam@dynamic@decode:decoder(ELB))
) -> gleam@dynamic@decode:decoder(ELB).
field(Field_index, Field_decoder, Next) ->
gleam@dynamic@decode:field(Field_index + 1, Field_decoder, Next).