use query_lang::{Database, QueryError, System};
type Cell = (u8, u8);
struct Sheet {
formulas: std::collections::BTreeMap<Cell, Vec<Cell>>,
evals: std::cell::Cell<u64>,
}
impl System for Sheet {
type Key = Cell;
type Value = i64;
fn compute(&self, db: &Database<Self>, cell: &Cell) -> Result<i64, QueryError> {
match self.formulas.get(cell) {
None => Ok(0),
Some(operands) => {
self.evals.set(self.evals.get() + 1);
let mut sum = 0;
for operand in operands {
sum += db.get(operand)?;
}
Ok(sum)
}
}
}
}
fn main() {
let mut formulas = std::collections::BTreeMap::new();
formulas.insert((b'C', 1), vec![(b'A', 1), (b'A', 2), (b'A', 3)]);
formulas.insert((b'C', 2), vec![(b'B', 1), (b'B', 2)]);
formulas.insert((b'D', 1), vec![(b'C', 1), (b'C', 2)]);
let mut sheet = Database::new(Sheet {
formulas,
evals: std::cell::Cell::new(0),
});
for (cell, value) in [
((b'A', 1), 10),
((b'A', 2), 20),
((b'A', 3), 30),
((b'B', 1), 5),
((b'B', 2), 7),
] {
sheet.set(cell, value);
}
let total = resolve(&sheet, (b'D', 1));
println!("initial D1 (grand total) = {total}");
println!(
" formula evaluations so far: {}",
sheet.system().evals.get()
);
println!("\nediting A2: 20 -> 25");
sheet.set((b'A', 2), 25);
let total = resolve(&sheet, (b'D', 1));
println!("D1 = {total}");
println!(
" formula evaluations after edit: {} (only C1 and D1 recomputed; C2 was reused)",
sheet.system().evals.get()
);
let before = sheet.system().evals.get();
println!("\nre-setting B1 to its current value (no real change)");
sheet.set((b'B', 1), 5);
let total = resolve(&sheet, (b'D', 1));
println!("D1 = {total}");
println!(
" formula evaluations: {} (unchanged — every query was a cache hit)",
sheet.system().evals.get()
);
debug_assert_eq!(sheet.system().evals.get(), before);
println!("\ncache metrics: {}", sheet.stats());
}
fn resolve(sheet: &Database<Sheet>, cell: Cell) -> i64 {
match sheet.get(&cell) {
Ok(value) => value,
Err(_) => {
println!(" #CYCLE! at {}{}", cell.0 as char, cell.1);
0
}
}
}