vsdb 16.3.2

A std-collection-like database
Documentation
# vsdb API Examples

This document provides examples for selected public APIs in the `vsdb` crate.

## Namespaces

All collection types support namespaces — independently-rooted engine instances
for physical placement, hard isolation, and O(1) bulk reclaim.  Everyday code
stays parameterless; placement is expressed through the object graph.

```rust
use vsdb::{Namespace, NamespaceOpts, basic::mapx::Mapx, basic::mapx_ord::MapxOrd};

// ---- Everyday tier: zero parameters, no names, no paths ----

let cold = Namespace::create().unwrap();

// Ambient placement: everything created inside scope() lands in `cold`.
let archive: Mapx<u64, String> = cold.scope(|| Mapx::new());

// Co-location: "put this data together with that data".
let index = Mapx::<u64, u64>::new_in(&archive.namespace());
assert_eq!(archive.namespace().id(), index.namespace().id());

// ---- Persist and recover via InstanceId ----

let id = archive.save_meta().unwrap();
// id.to_string() => "42@1"  (map 42 in namespace 1)
let restored: Mapx<u64, String> = Mapx::from_meta(id).unwrap();

// Bare u64 from pre-namespace code still works — targets the default namespace.
let old_id: u64 = archive.instance_id().into();
let from_old: Mapx<u64, String> = Mapx::from_meta(old_id).unwrap();

// ---- Advanced tier (opt-in): explicit volume, shards, budget ----

let fast = Namespace::create_with(NamespaceOpts {
    path: Some("/mnt/nvme/vsdb".into()),
    shards: 8,
    mem_budget_mb: 2048,
}).unwrap();
let hot: MapxOrd<i32, String> = MapxOrd::new_in(&fast);

// ---- Cross-namespace copy: `clone_in` (the `Clone` counterpart) ----

// Deep-copies into a brand-new instance placed in the target namespace
// (chunked; never whole-map in memory). Available on MapxRaw, Mapx,
// MapxOrd, MapxOrdRawKey, and Orphan.
let archive_copy = archive.clone_in(&Namespace::default_ns()).unwrap();

// Admin functions (from vsdb_core, re-exported by vsdb):
//   vsdb_ns_list() -> Result<Vec<NsInfo>>
//   vsdb_ns_close(id) -> Result<()>
//   ns.close()       -> consuming form; refusal returns the handle
//   vsdb_ns_destroy(id) -> Result<()>
//   vsdb_ns_relocate(id, new_path) -> Result<()>
```

Key rules:
- `Mapx::new()` targets the implicit default namespace — existing code needs zero changes.
- A composite structure (`VerMap`, `SlotDex`, …) always lives wholly inside one namespace.
- Cross-namespace atomic transactions do not exist (separate WALs).
- Reads, writes, and deserialization always route via the handle's own namespace —
  ambient scope affects creation only.
- Memory budgets are static and per-engine: the default namespace auto-sizes
  from the host/cgroup (¾-derated), every other namespace defaults to a fixed
  512 MB. Deployments that open many namespaces should pin the default engine
  with `VSDB_MEM_BUDGET_MB` (applied verbatim) and give each namespace an
  explicit `NamespaceOpts { mem_budget_mb, .. }` so the sum stays inside the
  process's memory line.

## Mapx

`Mapx` is a hash map-like data structure that stores key-value pairs.

```rust
use vsdb::Mapx;

let mut map: Mapx<String, String> = Mapx::new();

// Insert some key-value pairs
map.insert(&"key1".to_string(), &"value1".to_string());
map.insert(&"key2".to_string(), &"value2".to_string());

// Get a value
assert_eq!(map.get(&"key1".to_string()), Some("value1".to_string()));

// Check if a key exists
assert!(map.contains_key(&"key2".to_string()));

// Iterate over the key-value pairs
for (key, value) in map.iter() {
    println!("{}: {}", key, value);
}

// Remove a key-value pair
map.remove(&"key1".to_string());
```

## MapxOrd

`MapxOrd` is a B-tree map-like data structure that stores key-value pairs in a sorted order.

```rust
use vsdb::MapxOrd;

let mut map: MapxOrd<i32, String> = MapxOrd::new();

// Insert some key-value pairs
map.insert(&3, &"three".to_string());
map.insert(&1, &"one".to_string());
map.insert(&2, &"two".to_string());

// Get a value
assert_eq!(map.get(&1), Some("one".to_string()));

// Iterate over the key-value pairs in sorted order
for (key, value) in map.iter() {
    println!("{}: {}", key, value);
}

// Get the first and last key-value pairs
assert_eq!(map.first(), Some((1, "one".to_string())));
assert_eq!(map.last(), Some((3, "three".to_string())));
```

## VerMap

`VerMap` provides Git-style versioned storage with branching, commits, merge, and rollback (`rollback_to`).

For a detailed architecture guide with diagrams, see [Versioned Module — Architecture & Internals](versioned.md).

```rust
use vsdb::versioned::map::VerMap;
use vsdb::versioned::BranchId;

// 1. Create an empty versioned map (starts with a "main" branch).
let mut m: VerMap<u32, String> = VerMap::new();
let main = m.main_branch();

// 2. Write on the main branch and commit a snapshot.
m.insert(main, &1, &"hello".into()).unwrap();
m.commit(main).unwrap();

// 3. Fork a feature branch — cheap, no data copied.
let feat: BranchId = m.create_branch("feature", main).unwrap();
m.insert(feat, &1, &"updated".into()).unwrap();
m.commit(feat).unwrap();

// 4. Branches are isolated.
assert_eq!(m.get(main, &1).unwrap(), Some("hello".into()));
assert_eq!(m.get(feat, &1).unwrap(), Some("updated".into()));

// 5. Three-way merge: feature → main (source wins on conflict).
m.merge(feat, main).unwrap();
assert_eq!(m.get(main, &1).unwrap(), Some("updated".into()));

// 6. Clean up: deleting the branch reclaims unreachable commits and
//    B+ tree nodes automatically — no manual gc() call required
//    (gc() exists for crash recovery only).
m.delete_branch(feat).unwrap();
```

## MptCalc / SmtCalc (Merkle Trie)

`MptCalc` and `SmtCalc` are stateless, in-memory Merkle trie implementations:
they hold an ephemeral trie for root computation while all persistence and
versioning is handled by an external store (e.g. `VerMap`).

```rust,ignore
use vsdb::trie::MptCalc;

// Build a trie
let mut mpt = MptCalc::new();
mpt.insert(b"key1", b"value1").unwrap();
mpt.insert(b"key2", b"value2").unwrap();
// MPT keys must be at most 1024 bytes (MAX_MPT_KEY_LEN); insert,
// batch_update, and from_entries return an error for longer keys.
// SMT keys are hashed to a fixed 256-bit path and have no length limit.

// Compute the 32-byte Merkle root hash
let root = mpt.root_hash().unwrap();
assert_eq!(root.len(), 32);

// Lookup
assert_eq!(mpt.get(b"key1").unwrap(), Some(b"value1".to_vec()));

// Remove
mpt.remove(b"key1").unwrap();
assert_eq!(mpt.get(b"key1").unwrap(), None);

// Batch update
mpt.batch_update(&[
    (b"k1".as_ref(), Some(b"v1".as_ref())),
    (b"k2".as_ref(), None),  // remove
]).unwrap();

// Disposable cache (low-level API, used internally by VerMapWithProof):
// mpt.save_cache(cache_id, sync_tag).unwrap();
// let (loaded, tag, hash) = MptCalc::load_cache(cache_id).unwrap();
// When using VerMapWithProof, caching is fully automatic.
```

### SmtCalc with Proofs

```rust,ignore
use vsdb::trie::SmtCalc;

let mut smt = SmtCalc::new();
smt.insert(b"alice", b"100").unwrap();
smt.insert(b"bob", b"200").unwrap();

let root = smt.root_hash().unwrap();
let root32: [u8; 32] = root.try_into().unwrap();

// Membership proof
let proof = smt.prove(b"alice").unwrap();
assert_eq!(proof.value(), Some(b"100".as_ref()));
assert!(SmtCalc::verify_proof(&root32, b"alice", &proof).unwrap());

// Non-membership proof
let proof = smt.prove(b"charlie").unwrap();
assert_eq!(proof.value(), None);
assert!(SmtCalc::verify_proof(&root32, b"charlie", &proof).unwrap());
```

## VerMapWithProof

Integrates `VerMap` with `MptCalc` for versioned Merkle root computation.

```rust,ignore
use vsdb::trie::{MptCalc, VerMapWithProof};

let mut vmp: VerMapWithProof<Vec<u8>, Vec<u8>, MptCalc> = VerMapWithProof::new();
let main = vmp.map().main_branch();

// Write data and commit
vmp.map_mut().insert(main, &b"key1".to_vec(), &b"val1".to_vec()).unwrap();
vmp.map_mut().commit(main).unwrap();

// Compute the Merkle root (incrementally maintained)
let root = vmp.merkle_root(main).unwrap();
assert_eq!(root.len(), 32);

// Cache is auto-saved on Drop and auto-loaded on construction.
// No manual save_cache / load_cache calls needed.
```

## Slotdex

`SlotDex` (in the `slotdex` module) is a skip-list-like index for efficient, timestamp-based paged queries.

```rust,ignore
use vsdb::SlotDex64;  // SlotDex<u64, K> alias — slot type is u64

let mut db = SlotDex64::<String>::new(10u64, false); // tier_capacity must be >= 2

// Insert entries into slots (e.g., timestamps)
db.insert(100, "entry_a".to_string()).unwrap();
db.insert(100, "entry_b".to_string()).unwrap();
db.insert(200, "entry_c".to_string()).unwrap();
db.insert(300, "entry_d".to_string()).unwrap();

assert_eq!(db.total(), 4);

// Paged queries
let page = db.get_entries_by_page(2, 0, true);  // page_size=2, page_index=0, reverse=true
assert_eq!(page, vec!["entry_d".to_string(), "entry_c".to_string()]);

// Slot-range queries
let entries = db.get_entries_by_page_slot(Some(100), Some(200), 10, 0, false);
assert_eq!(entries.len(), 3);

// Remove
db.remove(100, &"entry_a".to_string());
assert_eq!(db.total(), 3);

// Bulk insertion (amortizes engine writes; ideal for imports/rebuilds)
db.insert_batch([(400u64, "entry_e".to_string()), (400, "entry_f".to_string())])
    .unwrap();
assert_eq!(db.total(), 5);
```