// main.mbt
//
// A small standalone demo of the library API: opens a database under
// demo-data/ (git-ignored), walks the whole delivered surface — point
// reads, batch writes, prefix/range scans, snapshot, flush, L1/L2
// compaction — and prints the results. Run with:
//
// moon run --target js demo
fn err_text(e : @QYF795/regolith.DbError) -> String {
match e {
@QYF795/regolith.DbError::Io(m) => m
@QYF795/regolith.DbError::Corrupt(m) => m
@QYF795/regolith.DbError::Fault(m) => m
}
}
/// Renders an Option[Option[Bytes]] read result for the demo output.
fn show(r : Option[Option[Bytes]]) -> String {
match r {
None => "(not found)"
Some(None) => "(deleted)"
Some(Some(v)) => view(v)
}
}
/// UTF-8 when valid, `0x…` hex otherwise.
fn view(bytes : Bytes) -> String {
try {
@utf8.decode(bytes)
} catch {
_ => {
let mut out = "0x"
for i = 0; i < bytes.length(); i = i + 1 {
let b = bytes[i].to_int() & 0xFF
out = out + (b.to_string())
}
out
}
}
}
/// Renders a full merge iterator as ` key = value` lines.
fn render(it : @QYF795/regolith.MergeIterator) -> String {
let mut out = ""
while true {
match it.next() {
None => break
Some(e) => match e.value {
None => ()
Some(v) => out = out + " " + view(e.key) + " = " + view(v) + "\n"
}
}
}
match it.error() {
Some(err) => out = out + " scan error: " + err_text(err) + "\n"
None => ()
}
out
}
/// Renders a range iterator the same way (scan_prefix/scan_range).
fn render_range(it : @QYF795/regolith.RangeIterator) -> String {
let mut out = ""
while true {
match it.next() {
None => break
Some(e) => match e.value {
None => ()
Some(v) => out = out + " " + view(e.key) + " = " + view(v) + "\n"
}
}
}
match it.error() {
Some(err) => out = out + " scan error: " + err_text(err) + "\n"
None => ()
}
out
}
fn stats_line(db : @QYF795/regolith.Db) -> String {
let s = db.stats()
"memtable=\{s.memtable_bytes}B wal=\{s.wal_bytes}B l0=\{s.l0_files} l1=\{s.l1_files} l2=\{s.l2_files}"
}
fn main {
println("regolith demo — 月壤 LSM KV store")
println("==================================\n")
try {
let db = @QYF795/regolith.Db::open("demo-data")
println("[put] hello=regolith, moon=bit, lsm=wal+memtable+sstable")
db.put(b"hello", b"regolith")
db.put(b"moon", b"bit")
db.put(b"lsm", b"wal + memtable + sstable")
println("[get] hello -> \{show(db.get(b"hello"))}")
println("[get] missing -> \{show(db.get(b"missing"))}\n")
println("[batch] one atomic commit: user:1=alice, user:2=bob, meta=v1")
db.write(
@QYF795/regolith.WriteBatch::new()
.put(b"user:1", b"alice")
.put(b"user:2", b"bob")
.put(b"meta", b"v1"),
)
println("[scan_prefix user:]")
println(render_range(db.scan_prefix(b"user:")))
println("[scan_range meta, user:1):")
println(render_range(db.scan_range(b"meta", Some(b"user:1"))))
println("[scan] all live entries:")
println(render(db.scan()))
println("[snapshot] frozen view before deleting moon")
let snap = db.snapshot()
println("[del] moon")
db.delete(b"moon")
println("[get] moon -> \{show(db.get(b"moon"))}")
// The snapshot still sees the state as of snapshot time.
println("[snapshot get] moon -> \{show(snap.get(b"moon"))}\n")
println("[snapshot scan] all entries as of snapshot time:")
println(render(snap.scan()))
snap.close()
println("[flush]")
db.flush()
println("[stats] \{stats_line(db)}")
println("\n[compact] merge everything into the bottom level (L2)")
db.compact()
db.compact_l2()
println("[stats] \{stats_line(db)}")
println("[get] hello -> \{show(db.get(b"hello"))}")
println("[get] moon -> \{show(db.get(b"moon"))}")
println("[scan] after compaction:")
println(render(db.scan()))
println("[close]")
db.close()
println("\n[corruption] 损坏检测:把落盘的 L2 表翻一个字节")
// 找到 demo-data 下的 L2 表,读进内存做一次字节翻转(磁盘文件不动,
// demo 可重复运行),然后让引擎重新解析:双层 CRC 必须拒绝它,
// 绝不静默返回错数据。
let names = try {
@fs.read_dir("demo-data").to_owned()
} catch {
_ => []
}
let mut l2_name = ""
for n in names {
if n.has_prefix("L2_") {
l2_name = n
}
}
if l2_name == "" {
println(" (no L2 table found — skipping)")
} else {
let good = try {
@fs.read_file_to_bytes("demo-data/" + l2_name)
} catch {
_ => b""
}
if good.length() == 0 {
println(" (could not read \{l2_name} — skipping)")
} else {
let _ = @QYF795/regolith.parse(good)
println(" intact table -> parses ok")
let mid = good.length() / 2
let bad = Bytes::makei(good.length(), fn(i) {
if i == mid {
(good[i].to_int() ^ 0xFF).to_byte()
} else {
good[i]
}
})
try {
let _ = @QYF795/regolith.parse(bad)
println(" NOT REJECTED — corruption slipped through!")
} catch {
e => println(" byte \{mid} flipped -> rejected: \{err_text(e)}")
}
}
}
println("done — data persisted under demo-data/")
} catch {
e => println("demo failed: \{err_text(e)}")
}
}