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
impl ModelStore
Sourcepub fn open(path: &str) -> Result<Self>
pub fn open(path: &str) -> Result<Self>
Open (or create) a store at path. Use "" for an in-memory database.
Sourcepub fn save_model(&mut self, model: &Model) -> Result<()>
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.
Sourcepub fn tx_high_water(&mut self) -> Result<i64>
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).
Sourcepub fn loaded_tx(&self) -> Option<i64>
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.
Sourcepub fn set_loaded_tx(&mut self, tx: Option<i64>)
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.
Sourcepub fn reload_model(&mut self) -> Result<Model>
pub fn reload_model(&mut self) -> Result<Model>
Re-read the model after a StoreError::Conflict; same as
ModelStore::load_model.
Sourcepub fn load_model(&mut self) -> Result<Model>
pub fn load_model(&mut self) -> Result<Model>
Reconstruct the model by querying the store.
Sourcepub fn load_partial(&mut self, measure_ids: &[MeasureId]) -> Result<Model>
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§
impl !Freeze for ModelStore
impl !RefUnwindSafe for ModelStore
impl !Sync for ModelStore
impl !UnwindSafe for ModelStore
impl Send for ModelStore
impl Unpin for ModelStore
impl UnsafeUnpin for ModelStore
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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