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]).
-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(EIC) :: any() | {gleam_phantom, EIC}.
-opaque table(EID) :: {table,
gleam@erlang@atom:atom_(),
list(table_attributes()),
gleam@erlang@charlist:charlist()} |
{gleam_phantom, EID}.
-type table_error() :: badarg | unable_to_open | unable_to_close.
-file("src\\database.gleam", 156).
?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"
" Ok(_) -> True\n"
" Error(Nil) -> False\n"
" }\n"
" }\n"
" ```\n"
).
-spec transaction(table(EJO), fun((table_ref(EJO)) -> EJR)) -> {ok, EJR} |
{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", 193).
?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(EJU), EJU) -> {ok, nil} | {error, any()}.
insert(Transac, Value) ->
case dets:insert(Transac, Value) of
{error, Reason} ->
{error, Reason};
_ ->
{ok, nil}
end.
-file("src\\database.gleam", 211).
?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", 233).
?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"
" Error(_) -> Error(PlutoNotFoundBlameTheAstronomers)\n"
" Ok(pluto) -> Ok(play_with(pluto))\n"
" }\n"
" }\n"
" ```\n"
).
-spec find(table_ref(EKB), any()) -> {ok, EKB} | {error, nil}.
find(Transac, Index) ->
case dets:lookup(Transac, Index) of
[Resp] ->
{ok, Resp};
_ ->
{error, nil}
end.
-file("src\\database.gleam", 256).
?DOC(
" Deletes de 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)\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", 263).
-spec is_record(any()) -> boolean().
is_record(Value) ->
erlang:is_tuple(Value) andalso erlang:is_atom(erlang:element(1, Value)).
-file("src\\database.gleam", 107).
?DOC(
" Creats a table.\n"
"\n"
" If no .dets file exists for the provided sample, 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(sample: pluto, index_at: 0)\n"
" // -> Ok(Table(Pet))\n"
" }\n"
" ```\n"
).
-spec create_table(EJK, integer()) -> {ok, table(EJK)} | {error, table_error()}.
create_table(Sample, Keypos) ->
case {is_record(Sample), Keypos >= 0} of
{true, true} ->
case (Keypos + 2) > erlang:tuple_size(Sample) of
true ->
{error, badarg};
false ->
At = erlang:element(1, Sample),
Name = erlang:atom_to_binary(At),
Path = unicode:characters_to_list(
<<Name/binary, ".dets"/utf8>>
),
Att = [{file, Path}, {type, set}, {keypos, Keypos + 2}],
case dets:open_file(At, Att) of
{ok, Tab} ->
case dets:close(Tab) of
{error, _} ->
{error, unable_to_close};
_ ->
{ok, {table, At, Att, Path}}
end;
{error, _} ->
{error, unable_to_open}
end
end;
{_, _} ->
{error, badarg}
end.