formualizer-eval 0.9.2

High-performance Arrow-backed Excel formula engine with dependency graph and incremental recalculation
Documentation
//! Correctness regressions retained from the historical tranche probe.

use crate::engine::{Engine, EvalConfig, FormulaPlaneMode};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;

fn config(mode: FormulaPlaneMode, threads: usize) -> EvalConfig {
    EvalConfig {
        enable_parallel: threads > 1,
        max_threads: Some(threads),
        formula_plane_mode: mode,
        arrow_storage_enabled: true,
        delta_overlay_enabled: true,
        write_formula_overlay_enabled: true,
        ..EvalConfig::default()
    }
}

fn formula(e: &mut Engine<TestWorkbook>, row: u32, col: u32, text: &str) {
    e.set_cell_formula("Sheet1", row, col, parse(text).unwrap())
        .unwrap();
}

fn dirty(e: &mut Engine<TestWorkbook>) {
    let ids: Vec<_> = e.graph.vertices_with_formulas().collect();
    for id in ids {
        e.graph.mark_vertex_dirty(id);
    }
    e.graph.mark_all_formula_spans_dirty(
        crate::engine::graph::WholeSpanDirtyReason::GlobalInvalidation,
    );
}

fn assert_number(e: &Engine<TestWorkbook>, row: u32, col: u32, expected: f64) {
    let v = e.get_cell_value("Sheet1", row, col).unwrap();
    let n = match v {
        LiteralValue::Number(n) => n,
        LiteralValue::Int(n) => n as f64,
        _ => panic!("{v:?}"),
    };
    assert_eq!(n, expected);
}

#[test]
fn criteria_whole_mask_work_is_independent_of_driver_chunks() {
    for mode in [
        FormulaPlaneMode::Off,
        FormulaPlaneMode::AuthoritativeExperimental,
    ] {
        for chunks in [1, 8, 32] {
            for text in [false, true] {
                let mut e = Engine::new(TestWorkbook::new(), config(mode, 1));
                let mut ingest = e.begin_bulk_ingest_arrow();
                ingest.add_sheet("Sheet1", 2, 256 / chunks);
                for _ in 0..256 {
                    let key = if text {
                        LiteralValue::Text("alpha".into())
                    } else {
                        LiteralValue::Number(1.0)
                    };
                    ingest
                        .append_row("Sheet1", &[key, LiteralValue::Number(2.0)])
                        .unwrap();
                }
                ingest.finish().unwrap();
                let pred = if text { "\"a*\"" } else { "\">0\"" };
                for p in [1, 4] {
                    let pairs = std::iter::repeat_n(format!("A1:A256,{pred}"), p)
                        .collect::<Vec<_>>()
                        .join(",");
                    for (expression, expected, calls) in [
                        (format!("=SUMIFS(B1:B256,{pairs})"), 512.0, p),
                        (format!("=COUNTIFS({pairs})"), 256.0, p),
                        (format!("=AVERAGEIFS(B1:B256,{pairs})"), 2.0, p),
                        (format!("=SUMIF(A1:A256,{pred},B1:B256)"), 512.0, 1),
                        (format!("=SUMIFS(B1:B256,FALSE,TRUE,{pairs})"), 0.0, 0),
                    ] {
                        formula(&mut e, 1, 4, &expression);
                        super::super::eval::criteria_mask_test_hooks::take_mask_work();
                        e.evaluate_all().unwrap();
                        assert_number(&e, 1, 4, expected);
                        assert_eq!(
                            super::super::eval::criteria_mask_test_hooks::take_mask_work(),
                            (calls, calls * 256),
                            "{mode:?} chunks={chunks} {expression}"
                        );
                    }
                }
            }
        }
    }
}

#[test]
fn lookup_mutation_boundaries_reclaim_and_readmit() {
    for mode in [
        FormulaPlaneMode::Off,
        FormulaPlaneMode::AuthoritativeExperimental,
    ] {
        for mutation in 0..4 {
            let mut cfg = config(mode, 1);
            cfg.lookup_index_cache_max_bytes = 512_000;
            let mut e = Engine::new(TestWorkbook::new(), cfg);
            for row in 1..=128 {
                e.set_cell_value("Sheet1", row, 1, LiteralValue::Number(row as f64))
                    .unwrap();
            }
            for row in 1..=12 {
                formula(&mut e, row, 3, "=MATCH(128,$A$1:$A$128,0)");
            }
            for cycle in 0..8 {
                dirty(&mut e);
                e.evaluate_all().unwrap();
                let report = e.last_lookup_index_cache_report();
                assert_eq!(report.builds, 1, "{mode:?} {mutation} {cycle} {report:?}");
                assert!(report.hits >= 8, "{report:?}");
                assert_eq!(report.entries_count, 1);
                assert_eq!(report.skipped_cap, 0);
                assert_number(&e, 1, 3, 128.0);
                dirty(&mut e);
                e.evaluate_all().unwrap();
                let warm = e.last_lookup_index_cache_report();
                assert_eq!(warm.builds, 0);
                assert_eq!(warm.hits, 12);
                assert_eq!(warm.bytes_in_cache, report.bytes_in_cache);
                assert_eq!(warm.entries_count, report.entries_count);
                match mutation {
                    0 => e.mark_data_edited(),
                    1 => e.mark_topology_edited(),
                    2 => e
                        .set_cell_value("Sheet1", 1, 6, LiteralValue::Number(cycle as f64))
                        .unwrap(),
                    _ => e
                        .set_cell_value("Sheet1", 1, 1, LiteralValue::Number(-(cycle as f64)))
                        .unwrap(),
                }
                let report = e.last_lookup_index_cache_report();
                assert_eq!(report.entries_count, 0);
                assert_eq!(report.bytes_in_cache, 0);
            }
        }
    }
}

#[test]
fn lookup_axis_edits_change_answers_with_active_spans() {
    use crate::engine::{FormulaIngestBatch, FormulaIngestRecord};
    for mode in [
        FormulaPlaneMode::Off,
        FormulaPlaneMode::AuthoritativeExperimental,
    ] {
        for text in [false, true] {
            let mut e = Engine::new(TestWorkbook::new(), config(mode, 1));
            let key = |i: u32| {
                if text {
                    LiteralValue::Text(format!("key-{i}"))
                } else {
                    LiteralValue::Number(i as f64)
                }
            };
            for row in 1..=128 {
                e.set_cell_value("Sheet1", row, 1, key(row)).unwrap();
                e.set_cell_value("Sheet1", row, 2, LiteralValue::Number(row as f64 * 10.0))
                    .unwrap();
            }
            e.set_cell_value("Sheet1", 1, 6, key(128)).unwrap();
            let mut records = Vec::new();
            for row in 1..=24 {
                for (col, expr) in [
                    (3, "=MATCH($F$1,$A$1:$A$128,0)"),
                    (4, "=VLOOKUP($F$1,$A$1:$B$128,2,FALSE)"),
                    (5, "=XLOOKUP($F$1,$A$1:$A$128,$B$1:$B$128)"),
                    (7, "=ABS(ROUND(ABS(ROUND($B$1,2)),2))"),
                ] {
                    let ast = e.intern_formula_ast(&parse(expr).unwrap());
                    records.push(FormulaIngestRecord::new(
                        row,
                        col,
                        ast,
                        Some(std::sync::Arc::<str>::from(expr)),
                    ));
                }
            }
            e.ingest_formula_batches(vec![FormulaIngestBatch::new("Sheet1", records)])
                .unwrap();
            let spans = e.baseline_stats().formula_plane_active_span_count;
            if mode == FormulaPlaneMode::AuthoritativeExperimental {
                assert_eq!(spans, 3);
                // MATCH, VLOOKUP and nested ABS/ROUND promote. XLOOKUP currently
                // retains its existing UnsupportedCanonicalTemplate fallback.
                assert_eq!(
                    e.last_formula_ingest_report()
                        .unwrap()
                        .graph_formula_cells_materialized,
                    24
                );
            } else {
                assert_eq!(spans, 0);
            }
            for cycle in 0..4 {
                if cycle > 0 {
                    e.set_cell_value("Sheet1", 1, 1, key(if cycle % 2 == 1 { 128 } else { 1 }))
                        .unwrap();
                    e.set_cell_value("Sheet1", 128, 1, key(if cycle % 2 == 1 { 1 } else { 128 }))
                        .unwrap();
                }
                dirty(&mut e);
                e.evaluate_all().unwrap();
                for row in 1..=24 {
                    assert_number(&e, row, 3, if cycle % 2 == 1 { 1.0 } else { 128.0 });
                    for col in [4, 5] {
                        assert_number(&e, row, col, if cycle % 2 == 1 { 10.0 } else { 1280.0 });
                    }
                    assert_number(&e, row, 7, 10.0);
                }
                let report = e.last_lookup_index_cache_report();
                assert_eq!(
                    report.builds,
                    if mode == FormulaPlaneMode::Off { 2 } else { 1 },
                    "{mode:?} {report:?}"
                );
                dirty(&mut e);
                e.evaluate_all().unwrap();
                let warm = e.last_lookup_index_cache_report();
                assert_eq!(warm.builds, 0);
                assert!(warm.hits > 0);
                assert_eq!(warm.bytes_in_cache, report.bytes_in_cache);
            }
        }
    }
}

#[test]
fn oversized_text_index_is_built_once_per_snapshot() {
    use crate::engine::lookup_index_cache::take_build_attempts;
    let mut cfg = config(FormulaPlaneMode::Off, 1);
    cfg.lookup_index_cache_max_bytes = 512_000;
    let mut e = Engine::new(TestWorkbook::new(), cfg);
    for row in 1..=128 {
        e.set_cell_value(
            "Sheet1",
            row,
            1,
            LiteralValue::Text(format!("{row}-{}", "X".repeat(4096))),
        )
        .unwrap();
    }
    for row in 1..=16 {
        formula(&mut e, row, 3, "=MATCH($A$1,$A$1:$A$128,0)");
    }
    for _ in 0..3 {
        take_build_attempts();
        dirty(&mut e);
        e.evaluate_all().unwrap();
        assert_eq!(take_build_attempts(), 1);
        for row in 1..=16 {
            assert_number(&e, row, 3, 1.0);
        }
        let report = e.last_lookup_index_cache_report();
        assert_eq!(report.skipped_cap, 13);
        assert_eq!(report.bytes_in_cache, 0);
        dirty(&mut e);
        e.evaluate_all().unwrap();
        assert_eq!(take_build_attempts(), 0);
        assert_eq!(e.last_lookup_index_cache_report().skipped_cap, 16);
        e.mark_data_edited();
    }
}