pub mod arrow_ingest;
pub mod effects;
pub mod eval;
pub mod eval_delta;
pub mod formula_ingest;
pub mod graph;
pub mod ingest;
pub mod ingest_builder;
pub(crate) mod ingest_pipeline;
pub mod journal;
pub mod lookup_index_cache;
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 arena::AstNodeId;
pub use eval::{
Engine, EngineAction, EngineBaselineStats, EvalResult, RecalcPlan, VirtualDepTelemetry,
};
pub use eval_delta::{DeltaMode, EvalDelta};
pub use formula_ingest::{FormulaIngestBatch, FormulaIngestRecord, FormulaIngestReport};
pub use journal::{ActionJournal, ArrowOp, ArrowUndoBatch, GraphUndoBatch};
pub use graph::snapshot::VertexSnapshot;
pub use graph::{
ChangeEvent, DependencyGraph, DependencyRef, GraphBaselineStats, 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};
#[doc(hidden)]
pub mod fp8_parity_test_support {
use super::{Engine, EvalConfig};
use crate::engine::arena::CanonicalLabels;
use crate::formula_plane::dependency_summary::summarize_canonical_template;
use crate::formula_plane::producer::SpanReadSummary;
use crate::formula_plane::runtime::{PlacementDomain, ResultRegion};
use crate::formula_plane::template_canonical::{
CanonicalRejectReason, CanonicalTemplateFlag, canonicalize_template,
};
use crate::reference::{CellRef, Coord};
use crate::traits::EvaluationContext;
use formualizer_common::{ExcelError, LiteralValue};
use formualizer_parse::parser::{ASTNode, parse};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct Fp8ParityObservation {
pub formula: String,
pub placement: CellRef,
pub old_payload: String,
pub new_hash: u64,
}
pub fn default_config() -> EvalConfig {
EvalConfig::default()
}
pub fn parse_formula(formula: &str) -> ASTNode {
parse(formula).unwrap_or_else(|err| panic!("parse {formula}: {err}"))
}
pub fn cell(sheet_id: u16, row: u32, col: u32) -> CellRef {
CellRef::new(sheet_id, Coord::from_excel(row, col, true, true))
}
pub fn assert_case<R: EvaluationContext>(
engine: &mut Engine<R>,
formula: &str,
placement: CellRef,
) -> Fp8ParityObservation {
let parsed = parse_formula(formula);
assert_case_ast(engine, formula, parsed, placement)
}
pub fn assert_case_ast<R: EvaluationContext>(
engine: &mut Engine<R>,
formula: &str,
parsed: ASTNode,
placement: CellRef,
) -> Fp8ParityObservation {
let mut old_ast = parsed.clone();
let old_rewrite = engine
.graph
.rewrite_structured_references_for_cell(&mut old_ast, placement);
let old = old_rewrite.and_then(|_| old_path(engine, &old_ast, placement));
let new = {
let mut pipeline = engine.ingest_pipeline();
pipeline.ingest_formula(
crate::engine::ingest_pipeline::FormulaAstInput::Tree(parsed),
placement,
Some(Arc::<str>::from(formula)),
)
};
match (old, new) {
(Ok(old), Ok(new)) => {
let new_direct = sorted_cells(new.dep_plan.direct_cell_deps.clone());
assert_eq!(
old.direct_cells, new_direct,
"direct deps differ for {formula} at {placement:?}\nold={:?}\nnew={:?}",
old.direct_cells, new_direct
);
assert_eq!(
old.range_deps, new.dep_plan.range_deps,
"range deps differ for {formula} at {placement:?}"
);
assert_eq!(
old.unresolved_names, new.dep_plan.named_refs,
"unresolved names differ for {formula} at {placement:?}"
);
assert_eq!(
old.volatile, new.dep_plan.volatile,
"volatile flag differs for {formula} at {placement:?}"
);
assert_eq!(
old.dynamic, new.dep_plan.dynamic,
"dynamic flag differs for {formula} at {placement:?}"
);
let mut expected_labels = canonical_labels_from_old(&old.labels);
if old.dynamic {
expected_labels.flags |= CanonicalLabels::FLAG_DYNAMIC;
}
assert_eq!(
expected_labels.flags, new.labels.flags,
"canonical label flags differ for {formula} at {placement:?}\nold={:?}\nnew={:#x}",
old.labels.flags, new.labels.flags
);
assert_eq!(
expected_labels.rejects, new.labels.rejects,
"canonical label rejects differ for {formula} at {placement:?}\nold={:?}\nnew={:#x}",
old.labels.reject_reasons, new.labels.rejects
);
assert_eq!(
old.read_summary_debug,
new.read_summary.as_ref().map(|s| format!("{s:?}")),
"read summary differs for {formula} at {placement:?}"
);
assert_eq!(new.formula_text.as_deref(), Some(formula));
assert_eq!(new.placement, placement);
Fp8ParityObservation {
formula: formula.to_string(),
placement,
old_payload: old.payload,
new_hash: new.canonical_hash,
}
}
(Err(old), Err(new)) => {
assert_eq!(
old.kind.to_string(),
new.kind.to_string(),
"old and new errored differently for {formula} at {placement:?}: old={old:?} new={new:?}"
);
Fp8ParityObservation {
formula: formula.to_string(),
placement,
old_payload: format!("ERR:{:?}", old.kind),
new_hash: 0,
}
}
(Ok(_), Err(new)) => panic!(
"new pipeline errored but old path succeeded for {formula} at {placement:?}: {new:?}"
),
(Err(old), Ok(_)) => panic!(
"old path errored but new pipeline succeeded for {formula} at {placement:?}: {old:?}"
),
}
}
#[derive(Debug)]
struct OldOutput {
payload: String,
labels: crate::formula_plane::template_canonical::CanonicalTemplateLabels,
direct_cells: Vec<CellRef>,
range_deps: Vec<crate::reference::SharedRangeRef<'static>>,
unresolved_names: Vec<String>,
volatile: bool,
dynamic: bool,
read_summary_debug: Option<String>,
}
fn old_path<R: EvaluationContext>(
engine: &mut Engine<R>,
ast: &ASTNode,
placement: CellRef,
) -> Result<OldOutput, ExcelError> {
let (_deps, ranges, placeholders, _named, unresolved_names) = engine
.graph
.fp8_parity_extract_dependencies_with_pending_names(ast, placement.sheet_id)?;
let volatile = engine.graph.fp8_parity_is_ast_volatile(ast);
let dynamic = engine.graph.is_ast_dynamic(ast);
let template =
canonicalize_template(ast, placement.coord.row() + 1, placement.coord.col() + 1);
let summary = summarize_canonical_template(&template);
let scalar_domain = PlacementDomain::row_run(
placement.sheet_id,
placement.coord.row(),
placement.coord.row(),
placement.coord.col(),
);
let result_region = ResultRegion::scalar_cells(scalar_domain);
let read_summary = SpanReadSummary::from_formula_summary(
placement.sheet_id,
&result_region,
&summary,
engine.graph.sheet_reg(),
)
.ok();
Ok(OldOutput {
payload: template.key.payload().to_string(),
labels: template.labels,
direct_cells: sorted_cells(placeholders),
range_deps: ranges,
unresolved_names,
volatile,
dynamic,
read_summary_debug: read_summary.as_ref().map(|s| format!("{s:?}")),
})
}
fn sorted_cells(mut cells: Vec<CellRef>) -> Vec<CellRef> {
cells.sort();
cells.dedup();
cells
}
fn canonical_labels_from_old(
old: &crate::formula_plane::template_canonical::CanonicalTemplateLabels,
) -> CanonicalLabels {
let mut labels = CanonicalLabels::default();
for flag in &old.flags {
labels.flags |= match flag {
CanonicalTemplateFlag::ParserVolatileFlag => CanonicalLabels::FLAG_VOLATILE,
CanonicalTemplateFlag::FunctionCall => CanonicalLabels::FLAG_CONTAINS_FUNCTION,
CanonicalTemplateFlag::CurrentSheetBinding => CanonicalLabels::FLAG_CURRENT_SHEET,
CanonicalTemplateFlag::ExplicitSheetBinding => CanonicalLabels::FLAG_EXPLICIT_SHEET,
CanonicalTemplateFlag::RelativeReferenceAxis => CanonicalLabels::FLAG_RELATIVE_ONLY,
CanonicalTemplateFlag::AbsoluteReferenceAxis => CanonicalLabels::FLAG_ABSOLUTE_ONLY,
CanonicalTemplateFlag::MixedAnchors => CanonicalLabels::FLAG_MIXED_ANCHORS,
CanonicalTemplateFlag::FiniteRangeReference => CanonicalLabels::FLAG_CONTAINS_RANGE,
};
}
for reason in &old.reject_reasons {
labels.flags |= match reason {
CanonicalRejectReason::DynamicReferenceFunction { .. } => {
CanonicalLabels::FLAG_DYNAMIC
}
CanonicalRejectReason::ParserVolatileFlag
| CanonicalRejectReason::VolatileFunction { .. } => CanonicalLabels::FLAG_VOLATILE,
CanonicalRejectReason::LocalEnvironmentFunction { .. } => {
CanonicalLabels::FLAG_CONTAINS_LET_LAMBDA
}
CanonicalRejectReason::ArrayOrSpillFunction { .. }
| CanonicalRejectReason::ArrayLiteral => CanonicalLabels::FLAG_CONTAINS_ARRAY,
CanonicalRejectReason::NamedReference { .. } => CanonicalLabels::FLAG_CONTAINS_NAME,
CanonicalRejectReason::StructuredReference { .. }
| CanonicalRejectReason::StructuredReferenceCurrentRow { .. } => {
CanonicalLabels::FLAG_CONTAINS_TABLE
| CanonicalLabels::FLAG_CONTAINS_STRUCTURED_REF
}
CanonicalRejectReason::OpenRangeReference { .. }
| CanonicalRejectReason::WholeAxisReference { .. } => {
CanonicalLabels::FLAG_CONTAINS_RANGE
}
_ => 0,
};
labels.rejects |= match reason {
CanonicalRejectReason::InvalidPlacementAnchor { .. } => {
CanonicalLabels::REJECT_INVALID_PLACEMENT_ANCHOR
}
CanonicalRejectReason::DynamicReferenceFunction { .. } => {
CanonicalLabels::REJECT_DYNAMIC_REFERENCE
}
CanonicalRejectReason::UnknownOrCustomFunction { .. } => {
CanonicalLabels::REJECT_UNKNOWN_OR_CUSTOM_FUNCTION
}
CanonicalRejectReason::LocalEnvironmentFunction { .. } => {
CanonicalLabels::REJECT_LOCAL_ENVIRONMENT
}
CanonicalRejectReason::ParserVolatileFlag => {
CanonicalLabels::REJECT_PARSER_VOLATILE_FLAG
}
CanonicalRejectReason::VolatileFunction { .. } => {
CanonicalLabels::REJECT_VOLATILE_FUNCTION
}
CanonicalRejectReason::ReferenceReturningFunction { .. } => {
CanonicalLabels::REJECT_REFERENCE_RETURNING_FUNCTION
}
CanonicalRejectReason::ArrayOrSpillFunction { .. } => {
CanonicalLabels::REJECT_ARRAY_OR_SPILL_FUNCTION
}
CanonicalRejectReason::ArrayLiteral => CanonicalLabels::REJECT_ARRAY_LITERAL,
CanonicalRejectReason::SpillReference { .. } => {
CanonicalLabels::REJECT_SPILL_REFERENCE
}
CanonicalRejectReason::SpillResultRegionOperator => {
CanonicalLabels::REJECT_SPILL_RESULT_REGION_OPERATOR
}
CanonicalRejectReason::ImplicitIntersectionOperator => {
CanonicalLabels::REJECT_IMPLICIT_INTERSECTION_OPERATOR
}
CanonicalRejectReason::CallExpression => CanonicalLabels::REJECT_CALL_EXPRESSION,
CanonicalRejectReason::NamedReference { .. } => {
CanonicalLabels::REJECT_NAMED_REFERENCE
}
CanonicalRejectReason::StructuredReference { .. } => {
CanonicalLabels::REJECT_STRUCTURED_REFERENCE
}
CanonicalRejectReason::StructuredReferenceCurrentRow { .. } => {
CanonicalLabels::REJECT_STRUCTURED_REFERENCE_CURRENT_ROW
}
CanonicalRejectReason::ThreeDReference { .. } => {
CanonicalLabels::REJECT_THREE_D_REFERENCE
}
CanonicalRejectReason::ExternalReference { .. } => {
CanonicalLabels::REJECT_EXTERNAL_REFERENCE
}
CanonicalRejectReason::OpenRangeReference { .. } => {
CanonicalLabels::REJECT_OPEN_RANGE_REFERENCE
}
CanonicalRejectReason::WholeAxisReference { .. } => {
CanonicalLabels::REJECT_WHOLE_AXIS_REFERENCE
}
CanonicalRejectReason::UnsupportedReference { .. } => {
CanonicalLabels::REJECT_UNSUPPORTED_REFERENCE
}
};
}
labels
}
pub fn literal_number(value: f64) -> LiteralValue {
LiteralValue::Number(value)
}
}
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 fn intern_formula_ast(&mut self, ast: &formualizer_parse::parser::ASTNode) -> AstNodeId {
self.graph.store_ast(ast)
}
}
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, Copy, Default, PartialEq, Eq)]
pub enum FormulaPlaneMode {
#[default]
Off,
Shadow,
AuthoritativeExperimental,
}
#[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,
pub formula_plane_mode: FormulaPlaneMode,
pub lookup_index_cache_max_bytes: usize,
}
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,
formula_plane_mode: FormulaPlaneMode::Off,
lookup_index_cache_max_bytes: 64 * 1024 * 1024,
}
}
}
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
}
#[inline]
pub fn with_formula_plane_mode(mut self, mode: FormulaPlaneMode) -> Self {
self.formula_plane_mode = mode;
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()
}
}