database

An outrageously simple set of functions to interact with the BEAM DETS (Disk-based Erlang Term Storage) API.

Good for small projects and POCs.

This project DOES NOT intend to serve as direct bindings to the DETS API, but rather to interact with it in a gleamy way:

  1. with a simple and concise interface;
  2. type-safely;
  3. no unexpected crashes, all errors are values.

Types

Possible error that may occur when using the library.

pub type FileError {
  UnableToOpen
  UnableToClose
}

Constructors

  • UnableToOpen

    A problem occurred when trying to open and lock the .dets file.

  • UnableToClose

    A problem ocurred when trying to write into the .dets file and close it.

Operations to perform on a select query.

pub type SelectOption(value) {
  Skip
  Continue(value)
  Done(value)
}

Constructors

  • Skip

    Ignores the current value.

  • Continue(value)

    Adds the value to the return list.

  • Done(value)

    Adds the value to the return list and immediately returns the query.

A collection of values used to access a DETS table.

pub opaque type Table(a)

A reference to an open table, required to interact with said table. Obtained through the transaction(Table) function

pub opaque type Transaction(a)

Values

pub fn create_table(
  name name: String,
  decode_with decoder: decode.Decoder(a),
) -> Result(Table(a), FileError)

Creats a table.

If no .dets file exists for the provided definition, creates one. Otherwise, just checks whether the file is accessible and not corrupted.

Example

pub fn start_database() {
  let decoder = {
    use name <- database.field(0, decode.string)
    use animal <- database.field(1, my_animal_decoder)
    decode.success(Pet(name:, animal:))
  }
  database.create_table(name: "pets", decode_with: decoder)
  // -> Ok(Table(Pet))
}
pub fn delete(
  transac: Transaction(a),
  id: String,
) -> Result(Nil, b)

Deletes a value from a table.

Example

pub fn delete_pet(table: Table(Pet) pet_id: String) {
  use ref <- database.transaction(table)
  database.delete(ref, pet_id)
}
pub fn drop_table(table: Table(a)) -> Result(Nil, b)

Deletes the entire table file

Example

pub fn destroy_all_pets(table: Table(Pet), password: String) {
  case password {
    "Yes, I am evil." -> {
      database.drop_table(table)
      Ok(Nil)
    }
    _ -> Error(WrongPassword)
  }
}
pub fn field(
  field_index: Int,
  field_decoder: decode.Decoder(t),
  next: fn(t) -> decode.Decoder(final),
) -> decode.Decoder(final)

Field decoder

Example

let decoder = {
  use name <- database.field(0, decode.string)
  use animal <- database.field(1, my_animal_decoder)
  decode.success(Pet(name:, animal:))
}
pub fn find(
  transac: Transaction(a),
  id: String,
) -> option.Option(a)

Finds a value by its index

Example

pub fn play_with_pluto(table: Table(Pet)) {
  use ref <- database.transaction(table)
  let resp = database.find(ref, known_pluto_id)
  case resp {
    None -> Error(PlutoNotFoundBlameTheAstronomers)
    Some(pluto) -> Ok(play_with(pluto))
  }
}
pub fn insert(
  transac: Transaction(a),
  value: a,
) -> Result(String, b)

Inserts a value into a table and return their generated id.

DETS tables do not have support for update, only for upsert. So if you have to change a value, just insert a new value with the same index, and it will replace the previous value.

Example

pub fn new_pet(table: Table(Pet), animal: Animal, name: String) -> String {
  let pet = Pet(name, animal)
  use ref <- database.transaction(table)
  database.insert(ref, pet)  
}
pub fn select(
  transac: Transaction(a),
  select_fn: fn(#(String, a)) -> SelectOption(b),
) -> List(b)

Searches for somethig on the table.

Example

pub fn fetch_all_parrots(table: Table(Pet)) {
  use ref <- database.transaction(table)
  use value <- database.select(ref)
  case value {
    #(_id, Pet(_name, Parrot)) -> Continue(value)
    _ -> Skip
  }
}

IMPORTANT

DETS tables are not sorted in any deterministic way, so never assume that the last value inserted will be the last one on the table.

pub fn transaction(
  table: Table(a),
  procedure: fn(Transaction(a)) -> b,
) -> Result(b, FileError)

Allows you to interact with the table.

It opens and locks the .dets file, then execute your operations. Once the operations are done, it writes the changes into the file, closes and releases it.

Example

pub fn is_pet_registered(table: Table(Pet), pet_id: String) {
  use ref <- database.transaction(table)
  case database.find(ref, pet_id) {
    Some(_) -> True
    None -> False
  }
}
Search Document