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§
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<()>
Sourcefn get_erased(
&self,
path: &StorePath,
f: &mut dyn FnMut(&mut dyn Deserializer<'_>) -> StorageResult<()>,
) -> StorageResult<bool>
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.
Sourcefn decode_erased(
&self,
bytes: &[u8],
f: &mut dyn FnMut(&mut dyn Deserializer<'_>) -> StorageResult<()>,
) -> StorageResult<()>
fn decode_erased( &self, bytes: &[u8], f: &mut dyn FnMut(&mut dyn Deserializer<'_>) -> StorageResult<()>, ) -> StorageResult<()>
Same, for bytes carried by a crate::StoreEvent.
fn delete_with_source( &self, path: &StorePath, source: Option<Uuid>, ) -> StorageResult<()>
fn delete(&self, path: &StorePath) -> StorageResult<()>
Sourcefn delete_prefix_with_source(
&self,
prefix: &StorePath,
source: Option<Uuid>,
) -> StorageResult<()>
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.
Sourcefn scan_prefix(
&self,
prefix: &StorePath,
) -> StorageResult<Vec<(StorePath, Vec<u8>)>>
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.
Sourcefn scan_keys(&self, prefix: &StorePath) -> StorageResult<Vec<StorePath>>
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.
Sourcefn save_now(&self) -> StorageResult<()>
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:?}");
}Sourcefn subscribe(
&self,
kind: SubscriptionKind,
callback: StoreCallback,
) -> SubscriptionId
fn subscribe( &self, kind: SubscriptionKind, callback: StoreCallback, ) -> SubscriptionId
Registers callback for the changes kind names, under an id from
SubscriptionId::next.
Sourcefn unsubscribe(&self, id: SubscriptionId) -> bool
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.
Sourcefn flush_prefix(&self, prefix: &StorePath) -> StorageResult<()>
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.
Sourcefn flush_async(&self) -> Commit ⓘ
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.
fn is_initialized(&self, namespace: &StorePath) -> StorageResult<bool>
Sourcefn set_initialized(
&self,
namespace: &StorePath,
state: InitState,
) -> StorageResult<()>
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§
fn delete_prefix(&self, prefix: &StorePath) -> StorageResult<()>
Sourcefn visit_prefix(
&self,
prefix: &StorePath,
visit: &mut dyn FnMut(PathRef<'_>, &[u8]) -> StorageResult<()>,
) -> StorageResult<()>
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.
Sourcefn parallel_reads(&self) -> bool
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.
Sourcefn files_layout(&self) -> Option<StoreLayout>
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.
Sourcefn persist_health(&self) -> Option<Arc<PersistHealth>>
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.
Sourcefn close(&self) -> StorageResult<()>
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.
Sourcefn is_closed(&self) -> bool
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.
Sourcefn save_async(&self) -> Commit ⓘ
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.
Sourcefn close_async(&self) -> Commit ⓘ
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.
Sourcefn record_schema(
&self,
at: &StorePath,
schema: &SchemaSnapshot,
) -> StorageResult<()>
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.
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§
impl StoreBackend for JsonStore
impl StoreBackend for MemoryStore
impl StoreBackend for RedbStore
impl StoreBackend for RonStore
impl StoreBackend for SqliteStore
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.