pub mod arrow_ingest;
pub mod effects;
pub mod eval;
pub mod eval_delta;
pub mod graph;
pub mod ingest;
pub mod ingest_builder;
pub mod journal;
pub mod plan;
pub mod range_view;
pub mod row_visibility;
pub mod scheduler;
pub mod spill;
pub mod vertex;
pub mod virtual_deps;
pub mod csr_edges;
pub mod debug_views;
pub mod delta_edges;
pub mod interval_tree;
pub mod named_range;
pub mod sheet_index;
pub mod sheet_registry;
pub mod topo;
pub mod vertex_store;
pub mod arena;
pub mod tuning;
#[cfg(test)]
mod tests;
pub use eval::{Engine, EngineAction, EvalResult, RecalcPlan, VirtualDepTelemetry};
pub use eval_delta::{DeltaMode, EvalDelta};
pub use journal::{ActionJournal, ArrowOp, ArrowUndoBatch, GraphUndoBatch};
pub use graph::snapshot::VertexSnapshot;
pub use graph::{
ChangeEvent, DependencyGraph, DependencyRef, OperationSummary, StripeKey, StripeType,
block_index,
};
pub use row_visibility::{RowVisibilitySource, VisibilityMaskMode};
pub use scheduler::{Layer, Schedule, Scheduler};
pub use vertex::{VertexId, VertexKind};
pub use graph::editor::{
DataUpdateSummary, EditorError, MetaUpdateSummary, RangeSummary, ShiftSummary, TransactionId,
VertexDataPatch, VertexEditor, VertexMeta, VertexMetaPatch,
};
pub use graph::editor::change_log::{ChangeLog, ChangeLogger, NullChangeLogger};
use crate::timezone::TimeZoneSpec;
use crate::traits::EvaluationContext;
use crate::traits::VolatileLevel;
use chrono::{DateTime, Utc};
use formualizer_common::error::{ExcelError, ExcelErrorKind};
use std::collections::HashMap;
impl<R: EvaluationContext> Engine<R> {
pub fn begin_bulk_ingest(&mut self) -> ingest_builder::BulkIngestBuilder<'_> {
ingest_builder::BulkIngestBuilder::new(&mut self.graph)
}
}
pub trait CalcObserver: Send + Sync {
fn on_eval_start(&self, vertex_id: VertexId);
fn on_eval_complete(&self, vertex_id: VertexId, duration: std::time::Duration);
fn on_cycle_detected(&self, cycle: &[VertexId]);
fn on_dirty_propagation(&self, vertex_id: VertexId, affected_count: usize);
}
impl CalcObserver for () {
fn on_eval_start(&self, _vertex_id: VertexId) {}
fn on_eval_complete(&self, _vertex_id: VertexId, _duration: std::time::Duration) {}
fn on_cycle_detected(&self, _cycle: &[VertexId]) {}
fn on_dirty_propagation(&self, _vertex_id: VertexId, _affected_count: usize) {}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeterministicMode {
Disabled {
timezone: TimeZoneSpec,
},
Enabled {
timestamp_utc: DateTime<Utc>,
timezone: TimeZoneSpec,
},
}
impl Default for DeterministicMode {
fn default() -> Self {
Self::Disabled {
timezone: TimeZoneSpec::default(),
}
}
}
impl DeterministicMode {
pub fn is_enabled(&self) -> bool {
matches!(self, DeterministicMode::Enabled { .. })
}
pub fn timezone(&self) -> &TimeZoneSpec {
match self {
DeterministicMode::Disabled { timezone } => timezone,
DeterministicMode::Enabled { timezone, .. } => timezone,
}
}
pub fn validate(&self) -> Result<(), ExcelError> {
if let DeterministicMode::Enabled { timezone, .. } = self {
timezone
.validate_for_determinism()
.map_err(|msg| ExcelError::new(ExcelErrorKind::Value).with_message(msg))?;
}
Ok(())
}
pub fn build_clock(
&self,
) -> Result<std::sync::Arc<dyn crate::timezone::ClockProvider>, ExcelError> {
self.validate()?;
Ok(match self {
#[cfg(feature = "system-clock")]
DeterministicMode::Disabled { timezone } => {
std::sync::Arc::new(crate::timezone::SystemClock::new(timezone.clone()))
}
#[cfg(not(feature = "system-clock"))]
DeterministicMode::Disabled { timezone: _ } => {
std::sync::Arc::new(crate::timezone::FixedClock::new(
chrono::DateTime::UNIX_EPOCH,
crate::timezone::TimeZoneSpec::Utc,
))
}
DeterministicMode::Enabled {
timestamp_utc,
timezone,
} => std::sync::Arc::new(crate::timezone::FixedClock::new(
*timestamp_utc,
timezone.clone(),
)),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormulaParsePolicy {
Strict,
CoerceToError,
KeepCachedValue,
AsText,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormulaParseDiagnostic {
pub sheet: String,
pub row: u32,
pub col: u32,
pub formula: String,
pub message: String,
pub policy: FormulaParsePolicy,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkbookLoadLimits {
pub max_sheet_rows: u32,
pub max_sheet_cols: u32,
pub max_sheet_logical_cells: u64,
pub sparse_sheet_cell_threshold: u64,
pub max_sparse_cell_ratio: u64,
}
impl Default for WorkbookLoadLimits {
fn default() -> Self {
Self {
max_sheet_rows: 1_048_576,
max_sheet_cols: 16_384,
max_sheet_logical_cells: 128_000_000,
sparse_sheet_cell_threshold: 250_000,
max_sparse_cell_ratio: 1_024,
}
}
}
#[derive(Debug, Clone)]
pub struct EvalConfig {
pub enable_parallel: bool,
pub max_threads: Option<usize>,
pub max_vertices: Option<usize>,
pub max_eval_time: Option<std::time::Duration>,
pub max_memory_mb: Option<usize>,
pub default_sheet_name: String,
pub case_sensitive_names: bool,
pub case_sensitive_tables: bool,
pub workbook_seed: u64,
pub volatile_level: VolatileLevel,
pub deterministic_mode: DeterministicMode,
pub range_expansion_limit: usize,
pub max_open_ended_rows: u32,
pub max_open_ended_cols: u32,
pub stripe_height: u32,
pub stripe_width: u32,
pub enable_block_stripes: bool,
pub spill: SpillConfig,
pub use_dynamic_topo: bool,
pub pk_visit_budget: usize,
pub pk_compaction_interval_ops: u64,
pub max_layer_width: Option<usize>,
pub pk_reject_cycle_edges: bool,
pub sheet_index_mode: SheetIndexMode,
pub warmup: tuning::WarmupConfig,
pub arrow_storage_enabled: bool,
pub delta_overlay_enabled: bool,
pub write_formula_overlay_enabled: bool,
pub max_overlay_memory_bytes: Option<usize>,
pub date_system: DateSystem,
pub formula_parse_policy: FormulaParsePolicy,
pub defer_graph_building: bool,
pub enable_virtual_dep_telemetry: bool,
}
impl Default for EvalConfig {
fn default() -> Self {
Self {
enable_parallel: true,
max_threads: None,
max_vertices: None,
max_eval_time: None,
max_memory_mb: None,
default_sheet_name: format!("Sheet{}", 1),
case_sensitive_names: false,
case_sensitive_tables: false,
workbook_seed: 0xF0F0_D0D0_AAAA_5555,
volatile_level: VolatileLevel::Always,
deterministic_mode: DeterministicMode::default(),
range_expansion_limit: 64,
max_open_ended_rows: 1_048_576,
max_open_ended_cols: 16_384,
stripe_height: 256,
stripe_width: 256,
enable_block_stripes: false,
spill: SpillConfig::default(),
use_dynamic_topo: false, pk_visit_budget: 50_000,
pk_compaction_interval_ops: 100_000,
max_layer_width: None,
pk_reject_cycle_edges: false,
sheet_index_mode: SheetIndexMode::Eager,
warmup: tuning::WarmupConfig::default(),
arrow_storage_enabled: true,
delta_overlay_enabled: true,
write_formula_overlay_enabled: true,
max_overlay_memory_bytes: None,
date_system: DateSystem::Excel1900,
formula_parse_policy: FormulaParsePolicy::Strict,
defer_graph_building: false,
enable_virtual_dep_telemetry: false,
}
}
}
impl EvalConfig {
#[inline]
pub fn with_range_expansion_limit(mut self, limit: usize) -> Self {
self.range_expansion_limit = limit;
self
}
#[inline]
pub fn with_parallel(mut self, enable: bool) -> Self {
self.enable_parallel = enable;
self
}
#[inline]
pub fn with_block_stripes(mut self, enable: bool) -> Self {
self.enable_block_stripes = enable;
self
}
#[inline]
pub fn with_case_sensitive_names(mut self, enable: bool) -> Self {
self.case_sensitive_names = enable;
self
}
#[inline]
pub fn with_case_sensitive_tables(mut self, enable: bool) -> Self {
self.case_sensitive_tables = enable;
self
}
#[inline]
pub fn with_arrow_storage(mut self, enable: bool) -> Self {
self.arrow_storage_enabled = enable;
self
}
#[inline]
pub fn with_delta_overlay(mut self, enable: bool) -> Self {
self.delta_overlay_enabled = enable;
self
}
#[inline]
pub fn with_formula_overlay(mut self, enable: bool) -> Self {
self.write_formula_overlay_enabled = enable;
self
}
#[inline]
pub fn with_date_system(mut self, system: DateSystem) -> Self {
self.date_system = system;
self
}
#[inline]
pub fn with_formula_parse_policy(mut self, policy: FormulaParsePolicy) -> Self {
self.formula_parse_policy = policy;
self
}
#[inline]
pub fn with_virtual_dep_telemetry(mut self, enable: bool) -> Self {
self.enable_virtual_dep_telemetry = enable;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SheetIndexMode {
Eager,
Lazy,
FastBatch,
}
pub use formualizer_common::DateSystem;
pub fn new_engine<R>(resolver: R, config: EvalConfig) -> Engine<R>
where
R: EvaluationContext + 'static,
{
Engine::new(resolver, config)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpillConfig {
pub conflict_policy: SpillConflictPolicy,
pub tiebreaker: SpillTiebreaker,
pub bounds_policy: SpillBoundsPolicy,
pub buffer_mode: SpillBufferMode,
pub memory_budget_bytes: Option<u64>,
pub cancellation: SpillCancellationPolicy,
pub visibility: SpillVisibility,
pub max_spill_cells: u32,
}
impl Default for SpillConfig {
fn default() -> Self {
Self {
conflict_policy: SpillConflictPolicy::Error,
tiebreaker: SpillTiebreaker::FirstWins,
bounds_policy: SpillBoundsPolicy::Strict,
buffer_mode: SpillBufferMode::ShadowBuffer,
memory_budget_bytes: None,
cancellation: SpillCancellationPolicy::Cooperative,
visibility: SpillVisibility::OnCommit,
max_spill_cells: 10_000,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpillConflictPolicy {
Error,
Preempt,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpillTiebreaker {
FirstWins,
EvaluationEpochAsc,
AnchorAddressAsc,
FunctionPriorityThenAddress,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpillBoundsPolicy {
Strict,
Truncate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpillBufferMode {
ShadowBuffer,
PersistenceJournal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpillCancellationPolicy {
Cooperative,
Strict,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpillVisibility {
OnCommit,
StagedLayer,
}
#[derive(Debug, Default)]
pub struct TombstoneRegistry {
pub pending_references: HashMap<String, Vec<VertexId>>,
}
impl TombstoneRegistry {
pub fn add_orphan(&mut self, sheet_name: String, vertex_id: VertexId) {
self.pending_references
.entry(sheet_name)
.or_default()
.push(vertex_id);
}
pub fn take_orphans(&mut self, sheet_name: &str) -> Vec<VertexId> {
self.pending_references
.remove(sheet_name)
.unwrap_or_default()
}
}