From e9b2a235ade7674a7dff3ea18855c86b3b7b3f0a Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 23:45:36 +0000 Subject: [PATCH] address review: stop matching database records once a computed criterion exhausts the work budget --- crates/calc/src/eval.rs | 3 +++ crates/functions/src/database.rs | 45 +++++++++++++++++++++++++++----- crates/functions/src/lib.rs | 5 ++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/crates/calc/src/eval.rs b/crates/calc/src/eval.rs index 2e8a1c409..9bf04ffbf 100644 --- a/crates/calc/src/eval.rs +++ b/crates/calc/src/eval.rs @@ -224,6 +224,9 @@ impl Ctx for FnCtx<'_, '_> { self.ev.active_criteria = active; Some(v) } + fn exhausted(&self) -> bool { + self.ev.exhausted + } fn has_lambda(&self) -> bool { self.lambda.is_some() } diff --git a/crates/functions/src/database.rs b/crates/functions/src/database.rs index 37289d9fb..8d5bf130e 100644 --- a/crates/functions/src/database.rs +++ b/crates/functions/src/database.rs @@ -94,12 +94,14 @@ fn matching_records(db: &Array, crit: &Array, ctx: &mut dyn Ctx) -> Vec { .filter(|&rec| { all_match || rows.iter().any(|(r, conds)| { - conds.iter().all(|(c, col, cr)| match col { - Some(f) => cr.matches(db.get(rec, *f).unwrap_or(&Value::Empty)), - // Under a label that names no field, only a formula (a computed - // criterion) can hold. - None => ctx.computed_criterion(*r, *c, db, rec).unwrap_or(false), - }) + // Out of work: a computed criterion would fail at once, so records are left out cheaply. + !ctx.exhausted() + && conds.iter().all(|(c, col, cr)| match col { + Some(f) => cr.matches(db.get(rec, *f).unwrap_or(&Value::Empty)), + // Under a label that names no field, only a formula (a computed + // criterion) can hold. + None => ctx.computed_criterion(*r, *c, db, rec).unwrap_or(false), + }) }) }) .collect() @@ -352,6 +354,37 @@ mod tests { is_err(d("DSUM", n(9.0)), CellError::Value); } + /// Computed criteria are no longer asked for once the host is out of work. + #[test] + fn matching_stops_asking_computed_criteria_when_exhausted() { + struct Spent { + asked: usize, + } + impl crate::Ctx for Spent { + fn date_system(&self) -> gridcraft_core::DateSystem { + gridcraft_core::DateSystem::D1900 + } + fn now_serial(&self) -> f64 { + 0.0 + } + fn random(&mut self) -> f64 { + 0.0 + } + fn computed_criterion(&mut self, _: usize, _: usize, _: &gridcraft_core::Array, _: usize) -> Option { + self.asked += 1; + Some(true) + } + fn exhausted(&self) -> bool { + self.asked > 0 + } + } + let criteria = arr(vec![vec![tv("Formula")], vec![nv(1.0)]]); + let mut ctx = Spent { asked: 0 }; + let spec = crate::lookup("DCOUNT").unwrap(); + crate::call(spec, &[rf(db()), t("Age"), rf(criteria)], &mut ctx); + assert_eq!(ctx.asked, 1); + } + #[test] fn simple_criteria() { let c = |cells: Vec| arr(vec![vec![tv("Tree"), tv("Height")], cells]); diff --git a/crates/functions/src/lib.rs b/crates/functions/src/lib.rs index ccc61b026..1af6dc57b 100644 --- a/crates/functions/src/lib.rs +++ b/crates/functions/src/lib.rs @@ -113,6 +113,11 @@ pub trait Ctx { fn computed_criterion(&mut self, _row: usize, _col: usize, _db: &Array, _record: usize) -> Option { None } + /// Whether the cell's work budget has run out: the database functions stop asking for computed + /// criteria then, as every one would fail at once. + fn exhausted(&self) -> bool { + false + } /// For GROUPBY/PIVOTBY: whether their function argument is a LAMBDA (its value is then not used). fn has_lambda(&self) -> bool { false