Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,21 @@ pub fn main() -> Nil {
let assert Ok(Nil) = database.delete(ref, "The Rains of Castemere")
})

// You can find elements by their primary_key...
let _ = database.transaction(table, fn(ref) {
let assert option.None = database.find(ref, "The Rains of Castemere")
let assert option.Some(Music("Templars", 2025)) = database.find(ref, "Templars")
})

// [...] or by complex queries
let assert Ok([_, _]) = database.transaction(table, fn(ref) {
database.select(ref, fn(value) {
case value {
Music(_, year) if year > 2000 -> database.Continue(value)
_ -> database.Skip
}
})
})
}
```

Expand Down
4 changes: 2 additions & 2 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "database"
version = "1.1.3"
description = "A BEAM-native embedded data storage"
version = "1.2.0"
description = "A very gleamy embedded data storage"

licences = ["MIT"]
repository = { type = "github", user = "R0DR160HM", repo = "database" }
Expand Down
60 changes: 60 additions & 0 deletions src/database.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ fn dets_delete(tab: TableRef(a), index: b) -> Result(b, c)
@external(erlang, "dets", "lookup")
fn dets_lookup(tab: TableRef(a), index: b) -> List(a)

@external(erlang, "dets", "traverse")
fn dets_traverse(
tab: TableRef(a),
select_fn: fn(a) -> SelectOption(b),
) -> List(b)

@external(erlang, "file", "delete")
fn file_delete(path: charlist.Charlist) -> a

Expand All @@ -66,6 +72,11 @@ fn erlang_is_atom(atom: Atom) -> Bool
@external(erlang, "erlang", "tuple_size")
fn erlang_tuple_size(tuple: a) -> Int

// Lying about the type ouput here is the only
// way to ensure type-safety on the `select` function
@external(erlang, "erlang", "binary_to_atom")
fn erlang_binary_to_atom(value: String) -> SelectOption(a)

// Type-safe API

/// A collection of values used to access a DETS table.
Expand Down Expand Up @@ -288,3 +299,52 @@ pub fn drop_table(table: Table(a)) {
fn is_record(value: a) {
erlang_is_tuple(value) && erlang_is_atom(erlang_element(1, value))
}

/// Operations to perform on a select query.
pub type SelectOption(value) {

/// Ignores the current value.
Skip

/// Adds the value to the return list.
Continue(value)

/// Adds the value to the return list
/// and immediately returns the query.
Done(value)
}

/// Searches for somethig on the table.
///
/// # Example
///
/// ```gleam
/// pub fn fetch_all_parrots(table: Table(Pet)) {
/// use ref <- database.transaction(table)
/// use value <- database.select(ref)
/// case value {
/// 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 select(
transac: TableRef(a),
select_fn: fn(a) -> SelectOption(b),
) -> List(b) {
let continue = erlang_binary_to_atom("continue")
let new_fn = fn(value: a) {
case select_fn(value) {
Skip -> continue
res -> res
}
}
dets_traverse(transac, new_fn)
}
// Ad maiorem Dei gloriam
33 changes: 33 additions & 0 deletions test/database_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,36 @@ pub fn avoid_table_colision_test() {
let assert Ok(_) = database.drop_table(t2)
let assert Error(_) = database.drop_table(t3)
}

pub fn select_test() {
let assert Ok(table) = database.create_table(definition, 0)

let _ =
database.transaction(table, fn(ref) {
let assert Ok(_) = database.insert(ref, Person("João", 23))
let assert Ok(_) = database.insert(ref, Person("Someone very old", 101))
let assert Ok(_) = database.insert(ref, Person("Maria", 55))
let assert Ok(_) = database.insert(ref, Person("Not Maria", 56))
})

let assert Ok([_, _]) =
database.transaction(table, fn(ref) {
database.select(ref, fn(value) {
case value {
Person("João", _) -> database.Continue(value)
Person(_, 55) -> database.Continue(value)
_ -> database.Skip
}
})
})

let assert Ok([Person("João", 23)]) =
database.transaction(table, fn(ref) {
database.select(ref, fn(value) {
case value {
Person("João", _) -> database.Done(value)
_ -> database.Skip
}
})
})
}