Duplicate Bag Tables
Duplicate bag tables work like bag tables but allow storing identical key-value pairs multiple times. Each insert adds a new copy, even if the exact same pair already exists.
Duplicate bag tables are provided by the slate/duplicate_bag module and correspond to the duplicate_bag table type in Erlang's DETS.
Opening and closing
Section titled “Opening and closing”import gleam/dynamic/decodeimport slate/duplicate_bag
let assert Ok(table) = duplicate_bag.open("data/events.dets", key_decoder: decode.string, value_decoder: decode.string)
// ... use the table ...
let assert Ok(Nil) = duplicate_bag.close(table)For short-lived operations, use with_table to close the table when the callback returns.
Inserting data
Section titled “Inserting data”// Each insert adds a new entry, even if the pair already existslet assert Ok(Nil) = duplicate_bag.insert(table, "click", "button_a")let assert Ok(Nil) = duplicate_bag.insert(table, "click", "button_a")let assert Ok(Nil) = duplicate_bag.insert(table, "click", "button_b")
// Batch insertlet assert Ok(Nil) = duplicate_bag.insert_list(table, [ #("error", "timeout"), #("error", "timeout"), #("error", "connection_refused"),])Looking up data
Section titled “Looking up data”Like bag tables, lookup returns a List of all values — including duplicates:
let assert Ok(clicks) = duplicate_bag.lookup(table, key: "click")// clicks == ["button_a", "button_a", "button_b"]
// Returns an empty list if the key doesn't existlet assert Ok([]) = duplicate_bag.lookup(table, key: "nonexistent")
// Check if a key existslet assert Ok(True) = duplicate_bag.member(table, key: "click")Deleting data
Section titled “Deleting data”// Delete all values for a keylet assert Ok(Nil) = duplicate_bag.delete_key(table, key: "click")
// Delete all occurrences of a specific key-value pairlet assert Ok(Nil) = duplicate_bag.insert(table, "click", "btn_a")let assert Ok(Nil) = duplicate_bag.insert(table, "click", "btn_a")let assert Ok(Nil) = duplicate_bag.insert(table, "click", "btn_b")let assert Ok(Nil) = duplicate_bag.delete_object(table, key: "click", value: "btn_a")// Only "btn_b" remains — both copies of "btn_a" were removed
// Clear all entrieslet assert Ok(Nil) = duplicate_bag.delete_all(table)Iterating over entries
Section titled “Iterating over entries”// Get all entries as a listlet assert Ok(entries) = duplicate_bag.to_list(table)
// Fold over entries (includes duplicates)let assert Ok(count) = duplicate_bag.fold(table, from: 0, with: fn(acc, _key, _value) { acc + 1})
// Get the number of stored objects (includes duplicates)let assert Ok(n) = duplicate_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) = duplicate_bag.sync(table)// Data is guaranteed to be on disk, table stays openTable info
Section titled “Table info”let assert Ok(info) = duplicate_bag.info(table)// info.file_size — size of the file on disk in bytes// info.object_count — number of entries (including duplicates)Opening with options
Section titled “Opening with options”Repair policy
Section titled “Repair policy”import slate.{AutoRepair}import gleam/dynamic/decode
let assert Ok(table) = duplicate_bag.open_with(path: "data/events.dets", repair: AutoRepair, key_decoder: decode.string, value_decoder: decode.string)Access mode
Section titled “Access mode”import slate.{AutoRepair, ReadOnly}import gleam/dynamic/decode
let assert Ok(table) = duplicate_bag.open_with_access(path: "data/events.dets", repair: AutoRepair, access: ReadOnly, key_decoder: decode.string, value_decoder: decode.string)Bag vs. Duplicate Bag
Section titled “Bag vs. Duplicate Bag”| Behavior | Bag | Duplicate Bag |
|---|---|---|
| Same key, different values | ✅ Stored | ✅ Stored |
| Same key, same value (duplicate pair) | ❌ Ignored | ✅ Stored |
delete_object removes | One pair | All copies of the pair |
When to use duplicate bag tables
Section titled “When to use duplicate bag tables”Duplicate bag tables are ideal for append-only or event-style data:
- Event logs: Record every occurrence, even repeats
- Audit trails: Track all actions including duplicates
- Time-series data: Store repeated measurements
- Counters by event: Count occurrences by folding over entries