Skip to main content

StoreBackend

Trait StoreBackend 

Source
pub trait StoreBackend:
    Send
    + Sync
    + 'static {
Show 28 methods // Required methods fn get_raw(&self, path: &StorePath) -> StorageResult<Option<Vec<u8>>>; fn set_erased( &self, path: &StorePath, value: &dyn Serialize, by: Writer, ) -> StorageResult<()>; fn set_owned_erased( &self, path: StorePath, value: &dyn Serialize, by: Writer, ) -> StorageResult<()>; fn get_erased( &self, path: &StorePath, f: &mut dyn FnMut(&mut dyn Deserializer<'_>) -> StorageResult<()>, ) -> StorageResult<bool>; fn decode_erased( &self, bytes: &[u8], f: &mut dyn FnMut(&mut dyn Deserializer<'_>) -> StorageResult<()>, ) -> StorageResult<()>; fn delete_with_source( &self, path: &StorePath, source: Option<Uuid>, ) -> StorageResult<()>; fn delete(&self, path: &StorePath) -> StorageResult<()>; fn delete_prefix_with_source( &self, prefix: &StorePath, source: Option<Uuid>, ) -> StorageResult<()>; fn scan_prefix( &self, prefix: &StorePath, ) -> StorageResult<Vec<(StorePath, Vec<u8>)>>; fn scan_keys(&self, prefix: &StorePath) -> StorageResult<Vec<StorePath>>; fn save_now(&self) -> StorageResult<()>; fn subscribe( &self, kind: SubscriptionKind, callback: StoreCallback, ) -> SubscriptionId; fn unsubscribe(&self, id: SubscriptionId) -> bool; fn flush_prefix(&self, prefix: &StorePath) -> StorageResult<()>; fn flush_async(&self) -> Commit ⓘ; fn is_initialized(&self, namespace: &StorePath) -> StorageResult<bool>; fn set_initialized( &self, namespace: &StorePath, state: InitState, ) -> StorageResult<()>; // Provided methods fn delete_prefix(&self, prefix: &StorePath) -> StorageResult<()> { ... } fn visit_prefix( &self, prefix: &StorePath, visit: &mut dyn FnMut(PathRef<'_>, &[u8]) -> StorageResult<()>, ) -> StorageResult<()> { ... } fn parallel_reads(&self) -> bool { ... } fn files_layout(&self) -> Option<StoreLayout> { ... } fn persist_health(&self) -> Option<Arc<PersistHealth>> { ... } fn close(&self) -> StorageResult<()> { ... } fn is_closed(&self) -> bool { ... } fn save_async(&self) -> Commit ⓘ { ... } fn close_async(&self) -> Commit ⓘ { ... } fn record_schema( &self, at: &StorePath, schema: &SchemaSnapshot, ) -> StorageResult<()> { ... } fn mark_initialized(&self, namespace: &StorePath) -> StorageResult<()> { ... }
}
Expand description

The store addressed by path, with nothing in the way.

Kv is the surface to reach for: it refuses a write at a path a declared struct owns, so a u16 field cannot be overwritten with a String by code that never saw the declaration. Nothing here does. A write through this trait lands wherever it is aimed, and delete_prefix takes the subtree it is given - declared paths included, and the initialization markers that decide whether defaults are seeded left behind.

Which is the point: the engines implement it, the schema layer is built on it, and a caller who knows exactly what they are addressing can use it. A caller who is guessing wants Kv.

Required Methods§

Source

fn get_raw(&self, path: &StorePath) -> StorageResult<Option<Vec<u8>>>

Source

fn set_erased( &self, path: &StorePath, value: &dyn Serialize, by: Writer, ) -> StorageResult<()>

Source

fn set_owned_erased( &self, path: StorePath, value: &dyn Serialize, by: Writer, ) -> StorageResult<()>

Source

fn get_erased( &self, path: &StorePath, f: &mut dyn FnMut(&mut dyn Deserializer<'_>) -> StorageResult<()>, ) -> StorageResult<bool>

Runs f against a deserializer positioned at path, in the backend’s own format. Ok(false) means the key is absent and f never ran.

Source

fn decode_erased( &self, bytes: &[u8], f: &mut dyn FnMut(&mut dyn Deserializer<'_>) -> StorageResult<()>, ) -> StorageResult<()>

Same, for bytes carried by a crate::StoreEvent.

Source

fn delete_with_source( &self, path: &StorePath, source: Option<Uuid>, ) -> StorageResult<()>

Source

fn delete(&self, path: &StorePath) -> StorageResult<()>

Source

fn delete_prefix_with_source( &self, prefix: &StorePath, source: Option<Uuid>, ) -> StorageResult<()>

Removes every key under prefix, emitting one crate::StoreOp::DeletePrefix instead of a Delete per key.

Source

fn scan_prefix( &self, prefix: &StorePath, ) -> StorageResult<Vec<(StorePath, Vec<u8>)>>

Every key under prefix, sorted by key on every backend.

Lists what StoreBackend::scan_keys lists.

Source

fn scan_keys(&self, prefix: &StorePath) -> StorageResult<Vec<StorePath>>

The keys under prefix, sorted, without reading their values.

scan_prefix copies every value out of the backend, which is wasted work when only the keys are wanted - and grows with the data rather than with the answer.

Every engine lists the same keys, and tests/a_scan_says_the_same_on_every_engine.rs is where that is stated rather than described. The one thing an engine can answer alone is a name no path can hold: a document may hold one, where a flat engine cannot, and the scan passes over it with a line at warn. Its value stays in the file and survives a save; nothing addresses it.

Source

fn save_now(&self) -> StorageResult<()>

Writes everything buffered and says whether it landed.

This is the fallible half of dropping the store, and the point of calling it is the Result: a full disk, a locked file or a permission error at exit is answered here, while the application is still running and can do something about it. Left to Drop, the same failure can only be logged.

The store goes on working afterwards, and a call with nothing buffered does nothing. The file is released when the last clone of the store goes.

let store = StoreBuilder::new(&*path).build().unwrap();
store.kv().set("port", &8080u16).unwrap();

if let Err(report) = store.save_now() {
    eprintln!("settings were not saved: {report:?}");
}
Source

fn subscribe( &self, kind: SubscriptionKind, callback: StoreCallback, ) -> SubscriptionId

Registers callback for the changes kind names, under an id from SubscriptionId::next.

Source

fn unsubscribe(&self, id: SubscriptionId) -> bool

Removes the subscription id names, and says whether this store held it. An id another store handed out is never held here.

Source

fn flush_prefix(&self, prefix: &StorePath) -> StorageResult<()>

Gets what is buffered under prefix onto disk.

How much else goes with it is the engine’s own answer - see Backend::a_commit_covers_the_whole_store.

Source

fn flush_async(&self) -> Commit ⓘ

Commits without blocking; the future resolves once a flush has landed.

Waiters ride on the flush the store was going to do anyway, so several of them cost one commit rather than one each.

Source

fn is_initialized(&self, namespace: &StorePath) -> StorageResult<bool>

Source

fn set_initialized( &self, namespace: &StorePath, state: InitState, ) -> StorageResult<()>

Records whether namespace has been seeded.

The one bit no amount of reading the data reproduces: a namespace whose values were all removed looks exactly like one that was never written. Which way it reads decides whether the next construction puts the declared defaults back.

Setting a namespace Fresh that was never seeded is not an error.

Provided Methods§

Source

fn delete_prefix(&self, prefix: &StorePath) -> StorageResult<()>

Source

fn visit_prefix( &self, prefix: &StorePath, visit: &mut dyn FnMut(PathRef<'_>, &[u8]) -> StorageResult<()>, ) -> StorageResult<()>

Hands every entry under prefix to visit, in the order a scan lists.

What it saves over StoreBackend::scan_prefix is everything that has to be built to hand an entry over as owned: a StorePath per key, which is a string and a walk, and a Vec per value, which is a copy out of the engine’s page. A caller that decodes each entry on the spot

  • which is what loading a map is - drops both immediately.

A key an engine holds as a string is checked on its way in, by PathRef::parse, so what reaches the visitor is a path and nothing above this trait has to know a key was ever spelled. Borrowed rather than owned, because the saving is the whole reason this exists: an owned path would allocate once per entry, which on a map is once per entry of the map.

Defaulted through scan_prefix, so a backend implemented outside this crate stays correct without knowing this exists.

Source

fn parallel_reads(&self) -> bool

Whether this store was asked to read large collections on more than one core - Redb::parallel_reads.

Defaulted so a backend implemented outside this crate need not know the question exists; answering false only means its reads stay on the calling thread. Of the engines here, redb and memory override it.

Source

fn files_layout(&self) -> Option<StoreLayout>

Where this store keeps what it keeps.

A caller that has to reach a store’s files - a backup tool, an uninstaller, a test - would otherwise rebuild their names from the one it was given, which means writing down a rule the engine owns. The engine says it instead.

Paths, not contents: a file is named whether or not it exists right now, because a backup exists only while a rewrite is in flight and its name is wanted either way.

This describes a store nobody is writing to. sqlite opens in WAL mode, so a live store holds committed data in a -wal sidecar until a checkpoint moves it across, and closing the store is what checkpoints. Copying the named files out from under a running store therefore takes a database missing its most recent commits - which is the condition a backup wants settled anyway, and the reason the sidecars are not named here: they belong to one engine and exist only while it is open.

None for a backend implemented outside this crate, which need not answer.

Source

fn persist_health(&self) -> Option<Arc<PersistHealth>>

How this store’s background saving is faring, and who is listening.

None for a backend implemented outside this crate, which saves on its own terms.

Source

fn close(&self) -> StorageResult<()>

Writes what is buffered, stops the background threads and lets go of the file.

This is what hands the file to somebody else - another process, a backup, a rename. Afterwards every read and write answers StorageError::Closed rather than opening it again, because taking it back would leave two owners each believing they hold it. Values already read stay readable in memory; it is the store that is closed, not the handles onto it.

It closes for every clone, since there is one file between them. Calling it more than once is fine, and Drop after it does nothing.

What each engine gives up differs: sqlite releases the file itself, redb releases its claim so another store can open it, and a document engine holds nothing open and only settles its threads.

The default writes what is buffered and leaves the store working, which is the whole of closing for a backend implemented outside this crate that holds nothing to give up. One that does hold something - a file handle, a connection, a thread - implements this and StoreBackend::is_closed together.

Source

fn is_closed(&self) -> bool

Whether StoreBackend::close has already run.

For a reader that holds a value of its own and wants to say where it came from: a closed store reports nothing further, so what such a reader holds is the last thing it was told.

Source

fn save_async(&self) -> Commit ⓘ

Writes everything buffered, as StoreBackend::save_now does, and resolves with how that write ended - without blocking the thread that awaits it.

One attempt, like save_now: a failure is answered as it happens, rather than after the retries it starts. StoreBackend::flush_async is the one that waits those out.

The default waits on flush_async, which is all a backend implemented outside this crate is asked for.

Source

fn close_async(&self) -> Commit ⓘ

Closes the store, as StoreBackend::close does, and resolves with what the closing write did - without blocking the thread that awaits it.

The default closes on the spot and answers at once, which suits a backend that holds nothing to give up.

Source

fn record_schema( &self, at: &StorePath, schema: &SchemaSnapshot, ) -> StorageResult<()>

Writes down the places a declaration claims, at the prefix it claims them under.

Every declaration is recorded at the open, from the inventory, because every one of them has a prefix known before anything is built. This is the door for a caller with a snapshot in hand - a migration that has just brought a prefix up to date, a test - rather than the ordinary way one gets written.

Without the record the store holds data under a path no recorded schema claims, which is the one question a tool reading the store on its own asks.

Writing the same shape again is not an error and costs nothing: the engines compare before they write, because a struct is built as often as the application likes.

Defaulted, so a backend implemented outside this crate stays correct without knowing the question exists - at the price of a tool learning nothing about what such a struct claimed there.

Source

fn mark_initialized(&self, namespace: &StorePath) -> StorageResult<()>

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl StoreBackend for JsonStore

Source§

impl StoreBackend for MemoryStore

Source§

impl StoreBackend for RedbStore

Source§

impl StoreBackend for RonStore

Source§

impl StoreBackend for SqliteStore

Source§

impl StoreBackend for Store

Addressing the store by path, with nothing in the way - and what that costs is written on StoreBackend itself, which is worth reading before reaching for any of it. The short of it: a write here lands wherever it is aimed, declared paths included, and Kv is the surface that refuses to.

Store::get and Store::set are the same operations under a shorter name, so they carry the same warning.

Source§

impl StoreBackend for TomlStore