Skip to main content

PimdirSourceStore

Struct PimdirSourceStore 

Source
pub struct PimdirSourceStore { /* private fields */ }
Available on crate feature client only.
Expand description

A pimdir store acting as one source ("left", "right", "phone", …): the sync seam, where every operation means “as this side”.

The underlying database and blobs are shared: several sources of one store are several handles over the same files. Dereferences to the PimdirStore it was made from, so the source-less surface stays reachable through it.

Implementations§

Source§

impl PimdirSourceStore

The sync seam and what only a side can mean: the source-bound writes, and the drain that stages a producer’s queued mutation for that side.

Source

pub fn source(&self) -> &str

The source this handle acts as.

Source

pub fn for_account(self, account: impl Into<String>) -> Self

Binds this handle to an account, so every collection it creates is grouped under it (spec §9.2); see PimdirStore::for_account, which this defers to so the two bindings can be given in either order.

Source

pub fn write_rekeyed( &mut self, collection: &str, ops: Vec<ReplicaWriteOp>, ) -> Result<i64, PimdirError>

Applies a handle-space rebuild’s write batch and bumps the collection’s generation in the same transaction, returning the new generation.

The owner drives io-replica’s rekey coroutine and routes its rebuild writes here rather than to write, so “the ids you cached are void” commits atomically with the rebuild that voided them. Ordinary syncs, full resyncs and content changes never bump.

Source

pub fn drain_collection( &mut self, collection: &str, ) -> Result<PimdirDrainReport, PimdirError>

Drains a collection’s pending actions in append order (spec §15.2).

Each action is applied as the store mutation it names, resolving its public seq to the internal link id and folding the corresponding io-replica mutation through the store’s own write machinery, and its row is deleted in the same transaction, so application is exactly-once and never partially visible. An action the owner judges permanently unappliable is parked with its error and skipped; a transient failure increments the row’s attempts and stops the pass, preserving apply order for the retry.

An action whose kind this store defines no semantics for is skipped: left pending, never parked, never blocking the actions behind it. That is what lets one queue carry store mutations any owner applies beside capability-bound intents only a specific owner can perform; that owner reads the row through pending_actions, performs it, and acknowledges it with drop_action.

Methods from Deref<Target = PimdirStore>§

Source

pub fn account(&self) -> Option<&str>

The account this handle writes under, None in a single-account store.

Source

pub fn load_hub(&self, collection: &str) -> Result<ReplicaHub, PimdirError>

Loads a collection’s full ReplicaHub: every source’s items and bindings, not only this handle’s source.

load projects the hub for one source; a multi-source consumer reads the whole hub to project each side and to spot items held by a single source.

Source

pub fn ensure_collection( &self, collection: &str, kind: &str, ) -> Result<(), PimdirError>

Declares a collection’s media type (kind), creating the collection if absent and updating its kind otherwise.

The kind is an IANA media type (message/rfc822, text/vcard, text/calendar, …), static consumer configuration rather than something the sync engine derives, so a consumer sets it out of band from the ReplicaStorage seam. That is what makes the store self-describing (§4.3) and lets one store hold several kinds. The lazy creation inside write uses ON CONFLICT DO NOTHING, so it never clobbers a kind set here.

The collection is grouped under this handle’s account (for_account); an existing row keeps the account it had, and only the kind is updated.

Source

pub fn set_collection_account( &self, collection: &str, account: Option<&str>, ) -> Result<(), PimdirError>

Regroups a collection under account, or out of one with None.

Safe at any time: the account partitions no identifier (spec §9.2), so the move leaves the collection’s seqs, link ids and objects alone.

Source

pub fn set_sort_key( &self, collection: &str, link_id: &str, sort_key: &str, ) -> Result<(), PimdirError>

Restates one item’s ordering key (spec §9.3).

For a re-projection: a store written before its kind had a sort-key convention, one whose convention changed, or a consumer deriving the key from the meta it wrote itself. Not part of the ordinary write path, which preserves a key by never naming it.

Source

pub fn rename_collection( &self, collection: &str, new_id: &str, ) -> Result<(), PimdirError>

Gives a collection a new id, carrying its whole contents with it.

Every foreign key onto collections(id) is ON UPDATE CASCADE, so the items, bindings, sources, queue rows and child collections follow in the same statement (spec §14). This is the only safe way to change an id: recreating the collection under the new one takes every item and binding with it through ON DELETE CASCADE, turning a rename into a full re-download and discarding any staged local change.

Two things make an id change: a server renaming the collection, and an owner renaming an account whose id it namespaced its collection ids with. An account rename is one call per collection; run them in one transaction and the account moves atomically.

Source

pub fn purge( &mut self, collection: &ReplicaCollectionId, seq: i64, ) -> Result<bool, PimdirError>

Purges one retained item by its public id, returning whether there was one to purge.

The row goes, its bindings cascade, and the body it released is unlinked by the ordinary sweep once nothing else references it: a purge collects nothing itself. A live item is never reached, the statement being guarded on the retention stamp, so an operator emptying the trash cannot destroy synced data.

Source

pub fn purge_retained_before( &mut self, cutoff: &str, ) -> Result<PimdirPurgeReport, PimdirError>

The scheduled sweep: purges every item retired strictly before cutoff (RFC 3339), store-wide, reporting how many it retired.

The boundary is the caller’s, not the store’s clock: an owner computes it from its own retention duration, so the store holds no policy and the sweep stays deterministic. An item retained exactly at cutoff is kept, and a cutoff of now reproduces the terminal-delete behaviour of a store that never retained, which is why there is no on/off switch.

Source

pub fn collect_garbage(&mut self) -> Result<PimdirGcReport, PimdirError>

Reclaims what nothing references: the object rows at refcount zero, the bodies they held, and any orphan blob a crash left behind.

Takes the store’s staging lock exclusively, so no producer is between a blob write and the queue row that pins it, and runs on an owning handle, which already holds the store against other owners. Those two let the sweep take a body the moment nothing references it, with no grace window standing in for a lock.

The rows go inside a transaction and the files after it, in the order a crash can afford: a body without its row is an orphan the next collection takes, where a row without its body fails a read.

Source

pub fn recompute_refcounts(&self) -> Result<usize, PimdirError>

Recomputes every object’s refcount from the five columns that pin one (spec §7), returning how many rows disagreed and were corrected.

The counterpart of the incremental maintenance every write does: a count that drifted, from a bug here or a foreign writer, is otherwise reported for ever. A whole-store pass, so it belongs to a repair verb rather than to a write.

Source

pub fn clear_dangling_bindings(&self) -> Result<usize, PimdirError>

Deletes the bindings whose item is gone, returning how many, and leaves every other dangling row alone.

A binding with no item is unreachable: nothing reads it and no sync projects it. The other dangling rows a check reports are not like that, an item whose object row is missing being still the item and a queue row whose body is missing still an intent, so deleting them would destroy data rather than repair it.

Source

pub fn drop_action(&mut self, id: i64) -> Result<bool, PimdirError>

Removes one queue row by request rather than by application, pending or parked, returning whether there was a row to remove (spec §15.5).

One verb for the two ways a row leaves the queue unapplied: a producer cancelling a queued action, and an owner acknowledging an intent it performed out of band, which the drain could only skip. The row’s body pin is released in the same transaction, so a blob nothing else references falls to the ordinary sweep.

Source

pub fn fail_action( &self, id: i64, error: Option<&str>, ) -> Result<(), PimdirError>

Records a failed apply an owner performed itself (spec §15.2).

None is the transient case: the attempt counter advances and the row stays pending for the next drain. Some(error) is the permanent one: the row parks with the failure, visible to operators instead of blocking its collection. An unknown id is a no-op, since the row may have been applied or cancelled meanwhile.

Methods from Deref<Target = PimdirReader>§

Source

pub fn object_stats(&self) -> Result<PimdirObjectStats, PimdirError>

How many objects are indexed and what they weigh in total.

Source

pub fn live_bytes(&self) -> Result<u64, PimdirError>

The bytes held by objects at least one live item still binds.

An object a live and a retained item share counts here, since purging the retained one would not free it.

Source

pub fn object_size(&self, hash: &str) -> Result<Option<u64>, PimdirError>

One object’s stored size.

Source

pub fn retained_before(&self, cutoff: &str) -> Result<(u64, u64), PimdirError>

What a purge with this cutoff would retire: how many retained items, and the bytes their bodies weigh.

A preview, so a confirmation can say what is at stake; the purge itself is the authority, and the collector is what frees the bytes.

Source

pub fn indexed_hashes(&self) -> Result<BTreeSet<String>, PimdirError>

Every hash the index knows, to diff against the blob directory: the index half of what PimdirBlobs::files reads from disk.

Source

pub fn refcount_drift(&self) -> Result<Vec<PimdirRefcountDrift>, PimdirError>

The objects whose stored refcount disagrees with their references.

The expected count is exactly what the write path maintains incrementally: an item’s body, an item’s conflict copy, each source’s stored base, and each queue row pinning a body it enqueued. recompute_refcounts is what settles what this reports.

Source

pub fn minted_keys(&self) -> Result<Vec<PimdirMinted>, PimdirError>

The minted keys each collection holds, where it holds any.

Not a defect and nothing to repair: two copies of one identity is redundancy, and the store holds both rather than judging them. It is reported because a collection whose count climbs every sync is a source handing over the same duplicate under a new handle each run, which an operator has no other way to see.

Source

pub fn dangling(&self) -> Result<Vec<PimdirDangling>, PimdirError>

Every row pointing at something absent: a binding whose item is gone, an item or a queue row whose object is not indexed.

Only the first is repairable (clear_dangling_bindings); the other two still hold data, so they are reported and left alone.

Source

pub fn overlays_pending(&self) -> bool

Whether this reader folds the pending queue over its item reads.

Source

pub fn hash_algo(&self) -> PimdirHashAlgo

The hash this store names its objects by (spec §5).

Source

pub fn blobs(&self) -> PimdirBlobs

A blob handle over this store’s object directory, bound to the hash the store names its bodies by.

Independent of the SQLite connection, so a body can be read while the store is mutably borrowed servicing a sync.

Source

pub fn hash(&self, bytes: &[u8]) -> ReplicaHash

The content hash of a whole body, under this store’s algorithm.

Source

pub fn hasher(&self) -> PimdirHasher

An incremental hasher for a body streamed into the blob store rather than held whole in memory, paired with PimdirBlobs::writer.

Source

pub fn collection_account( &self, collection: &str, ) -> Result<Option<Option<String>>, PimdirError>

The account a collection is grouped under.

The outer Option is whether the collection exists, the inner one whether it is grouped: Ok(None) for an unknown collection, Ok(Some(None)) for one in a single-account store.

Source

pub fn collection_kind( &self, collection: &str, ) -> Result<Option<String>, PimdirError>

The declared media type of a collection, or None if the store has never seen it. An empty string means the collection exists but was created lazily by a sync before any ensure_collection declared its kind.

Source

pub fn list_collections(&self) -> Result<Vec<PimdirCollection>, PimdirError>

Lists every collection in the store (client read surface).

Ordered by sort_order then id, unordered collections last. A direct getter: it observes the shared truth and never mutates, and writes go through io-replica’s write.

Source

pub fn list_collections_by_account( &self, account: Option<&str>, ) -> Result<Vec<PimdirCollection>, PimdirError>

Lists one account’s collections, the filter axis of a merged view (spec §9.2).

None selects the collections of a single-account store, matching on IS so a NULL account matches itself; = would match nothing.

Source

pub fn list_accounts(&self) -> Result<Vec<String>, PimdirError>

The accounts owning at least one collection.

Not a configured roster: a store learns an account only through its collections (spec §9.2), so one with none yet does not appear here and a consumer holding the real roster reads its own config.

Every live placement of one identity, with the collection and account it sits in (spec §9.2).

The store reports where a link id occurs and takes no position on whether the placements are one thing. A mail view lists them, two receipts of a newsletter having two read states; a contact view may offer to merge them. Both read these rows.

Source

pub fn object_placements( &self, hash: &str, ) -> Result<Vec<PimdirPlacement>, PimdirError>

Every live placement of one body, by content hash: the dedup axis rather than the identity one, so it pairs placements two servers gave different link ids.

Source

pub fn list_items( &self, collection: &str, after: Option<&str>, limit: usize, ) -> Result<Vec<PimdirItem>, PimdirError>

A keyset page of a collection’s live items (client read surface).

after is the exclusive lower bound on link_id, None starting from the beginning; at most limit items come back ordered by link_id, so the last item’s link_id is the next page’s cursor. Tombstones are excluded, and each item carries its level, so a body’s absence shows without probing the blobs.

Source

pub fn list_items_page_asc( &self, collection: &str, after: Option<(&str, i64)>, limit: usize, ) -> Result<Vec<PimdirItem>, PimdirError>

A keyset page of a collection’s live items in the kind’s own ascending order (spec §9.3): A to Z for contacts, earliest first for mail and calendars.

after is the previous page’s last (sort_key, seq), None starting from the beginning. The pair is the cursor because a sort key is not unique and seq, unique per collection, is what makes the page total: no item is skipped or repeated across a boundary.

Source

pub fn list_items_page_desc( &self, collection: &str, after: Option<(&str, i64)>, limit: usize, ) -> Result<Vec<PimdirItem>, PimdirError>

The same page descending: newest first for mail and calendars, Z to A for contacts.

None starts from the end, which the statement expresses by binding a key above every representable one, so a caller never invents that sentinel itself.

Source

pub fn get_item( &self, collection: &str, seq: i64, ) -> Result<Option<PimdirItem>, PimdirError>

One live item by its public id (collection, seq), or None. A tombstoned item reads as None, and the returned item carries its internal link_id for the caller to edit by.

Resolves an item’s public id (seq) from its internal link_id, the inverse of get_item, for a consumer that just staged an add and wants the id it now shows under.

Source

pub fn item_bindings( &self, collection: &str, link_id: &str, ) -> Result<BTreeMap<ReplicaSourceId, ReplicaSourceBinding>, PimdirError>

Every source’s binding of one item, keyed by source: the handle it is bound to, the base the last sync agreed on, and the conflict its own sync is stuck on (spec §13).

The same shape a hub carries per item, read for one item rather than a collection: an operator asking why a placement stopped moving is asking about exactly these columns, and nothing else exposes them.

Source

pub fn list_conflicts( &self, account: Option<&str>, ) -> Result<Vec<PimdirConflict>, PimdirError>

The bindings waiting for a decision, across one account’s collections, ordered by collection then link id then source.

None lists a single-account store whole, the account grouping nothing there. Each row carries the three bodies the divergence is between, so a resolver holding no credentials reads base, local and remote from the store alone (spec §13).

The question a sync answers at the end of every run, and the one a listing command asks directly. Both are served by the partial index over the flag, so a store with nothing outstanding pays for an empty index rather than for a pass over every collection.

Source

pub fn distinct_sources(&self) -> Result<Vec<String>, PimdirError>

The distinct source names the store has synced against, across all collections. A client attributes its writes with this: a store synced as a single source has exactly one, so the app writes as it without configuration.

Source

pub fn count_items(&self, collection: &str) -> Result<u64, PimdirError>

A collection’s live (non-tombstone) item count (client read surface).

Source

pub fn list_retained( &self, collection: &ReplicaCollectionId, after: Option<i64>, limit: usize, ) -> Result<Vec<PimdirItem>, PimdirError>

A keyset page of a collection’s retained items.

after is the exclusive lower bound on the public seq, None starting from the beginning; at most limit items come back ordered by seq, so the last item’s seq is the next page’s cursor. The only read that returns retained items: a caller presents them as a trash view, never merged into the live listing.

Source

pub fn count_retained( &self, collection: &ReplicaCollectionId, ) -> Result<i64, PimdirError>

A collection’s retained item count, the counterpart of count_items.

Source

pub fn retained_bytes(&self) -> Result<u64, PimdirError>

The bytes retention is holding across the whole store, each distinct body counted once.

An upper bound on what a purge would reclaim: a body a live item also points at keeps that reference and survives the sweep. Reported so an operator can price a retention duration.

Source

pub fn generation(&self, collection: &str) -> Result<Option<i64>, PimdirError>

A collection’s handle-space epoch (spec §12), or None when the store has never seen it. Starts at 1, bumped only by write_rekeyed, so a frontend derives an IMAP UIDVALIDITY from it alone.

Source

pub fn queued_collections(&self) -> Result<Vec<String>, PimdirError>

The collections with pending (non-parked) queue work, for the owner’s drain loop.

Source

pub fn pending_actions( &self, collection: &str, ) -> Result<Vec<PimdirPendingAction>, PimdirError>

A collection’s pending (non-parked) actions in append order, decoded (spec §15.4): a frontend overlays them on its item projection for read-your-writes. An undecodable payload errors, and the owner’s next drain parks such a row.

Source

pub fn parked_actions(&self) -> Result<Vec<PimdirParkedAction>, PimdirError>

Every parked action across the store, in append order, for status surfaces and operator repair. Parked rows are skipped by the drain and never silently deleted.

Source

pub fn pending_creates( &self, collection: &str, ) -> Result<Vec<PimdirPendingAction>, PimdirError>

The queued creates targeting a collection, in append order (spec §15.4).

Reported apart from the items because a create has no public id until the owner applies it, so there is nothing to address it by and no envelope to put it in. A consumer surfaces them its own way: a count under a listing, a queue view of its own, or the operator CLI’s.

Source

pub fn count_pending_creates( &self, collection: &str, ) -> Result<usize, PimdirError>

How many creates the collection has queued, the count a listing reports so a staged item reads as queued rather than as lost.

Trait Implementations§

Source§

impl Deref for PimdirSourceStore

Source§

type Target = PimdirStore

The resulting type after dereferencing.
Source§

fn deref(&self) -> &Self::Target

Dereferences the value.
Source§

impl DerefMut for PimdirSourceStore

Source§

fn deref_mut(&mut self) -> &mut Self::Target

Mutably dereferences the value.
Source§

impl ReplicaStorage for PimdirSourceStore

Source§

type Error = PimdirError

The error this storage raises.
Source§

fn load( &self, collection: &ReplicaCollectionId, scope: &ReplicaLoadScope, ) -> Result<ReplicaLoaded, Self::Error>

Loads a collection’s placements and checkpoint. Read more
Source§

fn lookup_objects( &self, links: &[ReplicaLinkId], ) -> Result<BTreeMap<ReplicaLinkId, ReplicaHash>, Self::Error>

Resolves which link ids already map to a stored object.
Source§

fn write(&mut self, ops: Vec<ReplicaWriteOp>) -> Result<(), Self::Error>

Applies a batch of writes atomically and in order, maintaining the pointer-derived object references ReplicaWriteOp documents. Read more

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<P, T> Receiver for P
where P: Deref<Target = T> + ?Sized, T: ?Sized,

Source§

type Target = T

🔬This is a nightly-only experimental API. (arbitrary_self_types)
The target type on which the method may be called.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

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

Source§

type Error = !

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.