Bag tables
Bag tables store multiple distinct values for the same key. An entry is one key-value pair. If a pair exists, insert returns Ok(Nil) without adding another copy. Use duplicate bag tables to store repeated copies of the same pair.
Bag tables are provided by the slate/bag module and correspond to the bag table type in Erlang's DETS.
Opening and closing
Section titled “Opening and closing”Create the parent directory before opening the table. open uses AutoRepair and ReadWrite.
import gleam/dynamic/decodeimport slateimport slate/bag
let assert Ok(table) = bag.open("data/tags.dets", key_decoder: decode.string, value_decoder: decode.string)
// ... use the table ...
let assert Ok(Nil) = bag.close(table)For short-lived operations, use with_table to close the table when the callback returns.
Inserting data
Section titled “Inserting data”// Insert multiple distinct values for one keylet assert Ok(Nil) = bag.insert(table, "color", "red")let assert Ok(Nil) = bag.insert(table, "color", "blue")
// An existing pair returns Ok(Nil) without adding another copylet assert Ok(Nil) = bag.insert(table, "color", "red")// The "color" key still has one "red" entry and one "blue" entry
// Batch insertlet assert Ok(Nil) = bag.insert_list(table, [ #("fruit", "apple"), #("fruit", "banana"), #("veggie", "carrot"),])Detect an existing pair
Section titled “Detect an existing pair”Use insert_new to return an error if the exact key-value pair exists. Other values for the same key do not prevent insertion.
let assert Ok(Nil) = bag.insert_new(table, "color", "green")let assert Error(slate.KeyAlreadyPresent) = bag.insert_new(table, "color", "green")Concurrent calls can both return Ok(Nil) for the same pair. The table still stores one copy. To ensure only one caller reports a new insertion, serialize writes through an owner process.
Looking up data
Section titled “Looking up data”On success, lookup returns Ok(values), where values is a list of all values for the key. The order is unspecified; do not assume insertion order.
let assert Ok(colors) = bag.lookup(table, key: "color")// Contains each stored color once, in an unspecified order
// Returns an empty list if the key does not existlet assert Ok([]) = bag.lookup(table, key: "nonexistent")
// Check if a key existslet assert Ok(True) = bag.member(table, key: "color")Deleting data
Section titled “Deleting data”// Delete all values for a keylet assert Ok(Nil) = bag.delete_key(table, key: "color")
// Delete a specific key-value pair, preserving other values for the keylet assert Ok(Nil) = bag.insert(table, "color", "red")let assert Ok(Nil) = bag.insert(table, "color", "blue")let assert Ok(Nil) = bag.delete_object(table, key: "color", value: "red")let assert Ok(["blue"]) = bag.lookup(table, key: "color")
// Clear all entrieslet assert Ok(Nil) = bag.delete_all(table)Iterating over entries
Section titled “Iterating over entries”// Get all entries in an unspecified order (loads the entire table into memory)let assert Ok(entries) = bag.to_list(table)
// Fold over entries in an unspecified orderlet assert Ok(count) = bag.fold(table, from: 0, with: fn(acc, _key, _value) { acc + 1})
// Count entries, not distinct keyslet assert Ok(n) = bag.size(table)Flushing writes
Section titled “Flushing writes”Use sync to flush pending writes to disk without closing the table:
let assert Ok(Nil) = bag.sync(table)// Pending updates are written to disk; the table remains openTable info
Section titled “Table info”let assert Ok(info) = bag.info(table)// info.file_size - size of the file on disk in bytes// info.object_count - number of entries, not distinct keys// info.file_path - absolute path of the table filefile_path uses the normalized path from open; it does not resolve symlinks.
For the 2.0 record change, see migration guidance.
Opening with options
Section titled “Opening with options”Repair policy
Section titled “Repair policy”Make a backup before you repair a file with data you need to keep. Repair does not guarantee recovery of all data. See Repair policies to choose an option.
import slate.{AutoRepair, ForceRepair, NoRepair}import gleam/dynamic/decode
let assert Ok(table) = bag.open_with(path: "data/tags.dets", repair: AutoRepair, key_decoder: decode.string, value_decoder: decode.string)Access mode
Section titled “Access mode”Use ReadOnly to prevent writes through the table handle. The file must exist.
import slate.{AutoRepair, ReadOnly}import gleam/dynamic/decode
let assert Ok(table) = bag.open_with_access(path: "data/tags.dets", repair: AutoRepair, access: ReadOnly, key_decoder: decode.string, value_decoder: decode.string)When to use bag tables
Section titled “When to use bag tables”Use bag tables to associate multiple distinct values with one key:
- Map an item to several tags or categories.
- Build secondary indexes over your data.
- Associate a user with several roles, or a post with several comments.
- Group related values under one key.