Skip to main content

ModelStore

Struct ModelStore 

Source
pub struct ModelStore { /* private fields */ }
Expand description

A model store backed by embedded Mentat (a single SQLite file, or "" for in-memory).

Implementations§

Source§

impl ModelStore

Source

pub fn open(path: &str) -> Result<Self>

Open (or create) a store at path. Use "" for an in-memory database.

Source

pub fn save_model(&mut self, model: &Model) -> Result<()>

Persist the entire model, atomically: either every category, item, measure, cell, view and meta-blob is durably saved, or (on any error) none of them are.

Idempotent for identity-unique entities (categories/items/measures keyed by their id; cells keyed by measure+coord), so re-saving updates in place.

Cells are diffed against the store, which ends up holding exactly model.inputs: unchanged cells add no datoms, and a stored cell the model lacks is retracted. After ModelStore::load_partial only the loaded closure’s cells are diffed that way; cells of measures the load skipped are left as they are.

Transacted in dependency order (categories, then items, then measures, then cells, then views, then meta) as several InProgress::transact calls sharing ONE underlying SQLite transaction (via Store::begin_transaction), so later lookup-refs resolve against earlier (still-uncommitted) writes in the same save. A single ip.commit() at the end makes the whole sequence all-or-nothing: if any step’s EDN fails to transact, ? returns before commit() runs and the dropped InProgress rolls back every prior step of this save. See .agent/steering/AGENT_DATABASE_CONNECTIVITY.md “Crash safety”.

Each step is further split into several transact calls of bounded datom count (see MAX_DATOMS_PER_TRANSACT), because Mentat aborts the process on a transact of 5461+ datoms. The extra calls are still inside the same single InProgress/commit(), so atomicity is unaffected.

Optimistic concurrency: if this handle loaded (or saved) the model, the save fails with StoreError::Conflict, writing nothing, when another writer has committed since. A handle that never loaded saves unconditionally.

Chunking cannot rescue a single entity wider than that ceiling, so a measure with more than MAX_CATEGORIES_PER_MEASURE (5460) categories is rejected up front with StoreError::Integrity — a recoverable error instead of a killed process.

Source

pub fn tx_high_water(&mut self) -> Result<i64>

The store’s latest transaction id, read live from SQLite (so it sees other processes’ commits).

Source

pub fn loaded_tx(&self) -> Option<i64>

The high-water this handle last loaded or saved at (None if it has done neither). Callers that reopen the store for each save carry it across handles with ModelStore::set_loaded_tx.

Source

pub fn set_loaded_tx(&mut self, tx: Option<i64>)

Adopt tx (an earlier handle’s ModelStore::loaded_tx) as the base save_model checks against. None saves unconditionally.

Source

pub fn reload_model(&mut self) -> Result<Model>

Re-read the model after a StoreError::Conflict; same as ModelStore::load_model.

Source

pub fn load_model(&mut self) -> Result<Model>

Reconstruct the model by querying the store.

Source

pub fn load_partial(&mut self, measure_ids: &[MeasureId]) -> Result<Model>

Load a model containing only the categories/items/measures/cells that an operation over measure_ids actually needs: measure_ids plus their transitive dependency closure (Model::measure_dependency_closure — every measure reachable via a Derived formula or an external-call’s arg_measures). Views/scenarios/external-fn defs/meta are still loaded in full (they’re small, bounded by model shape not data volume, not data-scale-sensitive like cells are).

This is the “windowed” load recommended in .agent/steering/AGENT_OUT_OF_CORE_DESIGN.md §4: engine::dataflow:: evaluate/engine::session::Engine::new need ZERO changes, because they already only touch whatever is in the Model they’re handed — the gap was purely that load_model always loaded every cell of every measure regardless of what the caller needed. Categories/items/measures are still loaded in full too (their volume scales with model shape, which is the cheap part; only load_cells, whose volume scales with data, is filtered). A single operation whose formula-dependency closure is a bounded subset of a huge model can now avoid paying for every other measure’s cells — it does NOT help an operation that touches the whole model by definition (e.g. a grand total over everything).

The cell filter is applied in the store: load_cells binds ?mid to exactly the closure’s measure ids with ground, so the query returns rows only for those measures — no row is materialized or returned for a cell outside the closure. See load_cells for the one residual cost this does NOT remove (SQLite still scans the cell datom slices inside the query; only the returned row set is closure-bounded).

Saving the result with ModelStore::save_model writes the closure’s cells and leaves every other measure’s cells alone.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.