pub trait ObjectStore:
SidecarStore
+ Send
+ Sync {
Show 47 methods
// Required methods
fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>>;
fn put_blob(&self, blob: &Blob) -> Result<ContentHash>;
fn has_blob(&self, hash: &ContentHash) -> Result<bool>;
fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>>;
fn put_tree(&self, tree: &Tree) -> Result<ContentHash>;
fn has_tree(&self, hash: &ContentHash) -> Result<bool>;
fn get_state(&self, id: &StateId) -> Result<Option<State>>;
fn put_state(&self, state: &State) -> Result<()>;
fn has_state(&self, id: &StateId) -> Result<bool>;
fn list_states(&self) -> Result<Vec<StateId>>;
fn get_action(&self, id: &ActionId) -> Result<Option<Action>>;
fn put_action(&self, action: &mut Action) -> Result<ActionId>;
fn list_actions(&self) -> Result<Vec<ActionId>>;
fn list_blobs(&self) -> Result<Vec<ContentHash>>;
fn list_trees(&self) -> Result<Vec<ContentHash>>;
// Provided methods
fn get_annotated_tag(
&self,
_hash: &ContentHash,
) -> Result<Option<AnnotatedTag>> { ... }
fn put_annotated_tag(&self, _tag: &AnnotatedTag) -> Result<ContentHash> { ... }
fn list_annotated_tags(&self) -> Result<Vec<ContentHash>> { ... }
fn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<Bytes>> { ... }
fn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>> { ... }
fn loose_blob_path(&self, _hash: &ContentHash) -> Option<PathBuf> { ... }
fn promote_to_loose_uncompressed(&self, _hash: &ContentHash) -> Result<bool> { ... }
fn clear_recent_caches(&self) { ... }
fn put_blob_with_hash(
&self,
blob: &Blob,
hash: ContentHash,
) -> Result<ContentHash> { ... }
fn has_blob_locally(&self, hash: &ContentHash) -> Result<bool> { ... }
fn has_tree_locally(&self, hash: &ContentHash) -> Result<bool> { ... }
fn open_tree(
&self,
tree_id: &ContentHash,
cursor: Option<&TreeResumeCursor>,
) -> Result<Option<TreeEntryReader<OpenedTreeBody>>> { ... }
fn get_state_attachment(
&self,
_state: &StateId,
_id: &StateAttachmentId,
) -> Result<Option<StateAttachment>> { ... }
fn put_state_attachment(
&self,
_attachment: &StateAttachment,
) -> Result<StateAttachmentId> { ... }
fn list_state_attachments(
&self,
_state: &StateId,
) -> Result<Vec<StateAttachment>> { ... }
fn put_blob_bytes_with_hash(
&self,
data: &[u8],
hash: ContentHash,
) -> Result<ContentHash> { ... }
fn get_tree_serialized(&self, hash: &ContentHash) -> Result<Option<Vec<u8>>> { ... }
fn put_tree_serialized(
&self,
data: &[u8],
hash: ContentHash,
) -> Result<ContentHash> { ... }
fn put_state_serialized(&self, data: &[u8], id: StateId) -> Result<()> { ... }
fn put_action_serialized(&self, data: &[u8], id: ActionId) -> Result<()> { ... }
fn get_pack_object(
&self,
id: &PackObjectId,
) -> Result<Option<(ObjectType, Vec<u8>)>> { ... }
fn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()> { ... }
fn put_snapshot_objects_packed(
&self,
blobs: Vec<(ContentHash, Vec<u8>)>,
tree: &Tree,
state: &State,
) -> Result<()> { ... }
fn put_snapshot_objects_and_attachments_packed(
&self,
blobs: Vec<(ContentHash, Vec<u8>)>,
tree: &Tree,
state: &State,
attachments: Vec<StateAttachment>,
) -> Result<()> { ... }
fn install_pack(
&self,
pack_data: &[u8],
index_data: &[u8],
) -> Result<Vec<PackObjectId>> { ... }
fn install_pack_streaming(
&self,
pack_path: &Path,
index_path: &Path,
) -> Result<Vec<PackObjectId>> { ... }
fn pack_objects(&self, delta_search: bool) -> Result<(u64, u64)> { ... }
fn prune_loose_objects(&self) -> Result<(u64, u64)> { ... }
fn discard_corrupt_clone_packs(&self) -> Result<usize> { ... }
fn begin_snapshot_write_batch(&self) -> Result<()> { ... }
fn flush_snapshot_write_batch(&self) -> Result<()> { ... }
fn abort_snapshot_write_batch(&self) { ... }
}Expand description
Trait for object storage backends.
Sidecars remain a separate implementation seam, but every object store
exposes that seam. This preserves object-safe dyn ObjectStore consumers
such as Weft’s local filesystem backend without coupling its S3 backend to
the native store implementation.
Required Methods§
fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>>
fn put_blob(&self, blob: &Blob) -> Result<ContentHash>
fn has_blob(&self, hash: &ContentHash) -> Result<bool>
fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>>
fn put_tree(&self, tree: &Tree) -> Result<ContentHash>
fn has_tree(&self, hash: &ContentHash) -> Result<bool>
fn get_state(&self, id: &StateId) -> Result<Option<State>>
fn put_state(&self, state: &State) -> Result<()>
fn has_state(&self, id: &StateId) -> Result<bool>
fn list_states(&self) -> Result<Vec<StateId>>
fn get_action(&self, id: &ActionId) -> Result<Option<Action>>
fn put_action(&self, action: &mut Action) -> Result<ActionId>
fn list_actions(&self) -> Result<Vec<ActionId>>
fn list_blobs(&self) -> Result<Vec<ContentHash>>
fn list_trees(&self) -> Result<Vec<ContentHash>>
Provided Methods§
fn get_annotated_tag(&self, _hash: &ContentHash) -> Result<Option<AnnotatedTag>>
fn put_annotated_tag(&self, _tag: &AnnotatedTag) -> Result<ContentHash>
Sourcefn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<Bytes>>
fn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<Bytes>>
Zero-copy variant of get_blob. Returns a bytes::Bytes
view of the blob’s content, which for FsStore reads is a
slice into the pack file’s mmap when the entry is non-delta
and uncompressed — no allocation, no memcpy.
Default impl wraps get_blob’s Vec<u8> in a Bytes (one
Arc allocation, no body copy) so backends without a native
fast path still satisfy the contract. The mount’s hot read
path goes through this method instead of get_blob so the
pack-mmap fast path lights up automatically.
Sourcefn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>>
fn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>>
Return the uncompressed byte length of the blob identified by
hash, or Ok(None) when the blob is not in the store.
The contract is “size without paying for content”: backends are
expected to honour this with a header read or index lookup
rather than a full decompression. This is the hot path for
directory listings (ls -l over a thread mount) where loading
every blob just to learn its size would dominate.
The default implementation falls back to get_blob so backends
without a cheap size accessor still satisfy the contract; native
stores (FsStore, InMemoryStore) override this with a
header- or hashmap-only path.
Sourcefn loose_blob_path(&self, _hash: &ContentHash) -> Option<PathBuf>
fn loose_blob_path(&self, _hash: &ContentHash) -> Option<PathBuf>
Filesystem path of the loose blob whose on-disk bytes are
byte-identical to the blob’s uncompressed content, suitable
for hard_link/clonefile materialization without going
through get_blob.
Returns None when the blob is missing, is only available via
a packfile, is stored compressed (the on-disk bytes wouldn’t
match what a worktree consumer needs to read), or the backend
doesn’t expose stable filesystem paths (e.g. InMemoryStore). The
default impl returns None so non-FsStore backends silently fall
through to the bytes path.
Sourcefn promote_to_loose_uncompressed(&self, _hash: &ContentHash) -> Result<bool>
fn promote_to_loose_uncompressed(&self, _hash: &ContentHash) -> Result<bool>
Ensure the blob identified by hash is materialized as an
uncompressed loose file at the canonical loose path so that
loose_blob_path returns Some(path) on a subsequent call.
This is the “warm canonical store” path that lets the
hardlink-first materializer keep its 5–10× wall-clock and
storage-allocation wins after pack_objects + prune_loose_objects
has moved everything into a packfile. Without this, the lazy
hardlink path silently degrades to fs::write(decompressed) on
every materialize, because loose_blob_path returns None for
pack-only and compressed-loose blobs.
Cost-amortization: the first promotion of a blob pays
decompress + atomic write. Every subsequent materialize of
the same blob — into the same worktree on goto, or into a
sibling worktree on delegate — is a single link(2). Net
win for any N > 1 materializations; break-even at N == 1.
Pack invariants are preserved: this method does not remove the
pack-resident copy. The blob lives in both pack and loose-
uncompressed until the next prune_loose_objects cycle, at
which point the loose mirror is discarded and a future
materialize re-promotes on demand.
Idempotent: a blob that’s already loose-and-uncompressed is a no-op fast path. A blob that’s loose-but-compressed is rewritten in place (atomically) with the uncompressed bytes. A blob that’s pack-resident is decompressed out of the pack and written loose without touching the pack.
Returns Ok(true) when the call did real work (a write
happened), Ok(false) when it was a no-op (blob was already
loose+uncompressed), and Err when the blob isn’t in the
store at all. The default impl returns Ok(false) for
backends that don’t expose loose paths (InMemoryStore), since the
hardlink path is fundamentally inapplicable there.
Sourcefn clear_recent_caches(&self)
fn clear_recent_caches(&self)
Drop any in-memory caches of decompressed blobs / trees /
states. The next access to any object pays full I/O +
decompression cost. No-op for stores that don’t cache
(InMemoryStore is already the source of truth).
Exposed primarily for benchmarks that want to measure the true cold-cache path without rebuilding the store from scratch. Production callers don’t need to invoke this.
fn put_blob_with_hash( &self, blob: &Blob, hash: ContentHash, ) -> Result<ContentHash>
Sourcefn has_blob_locally(&self, hash: &ContentHash) -> Result<bool>
fn has_blob_locally(&self, hash: &ContentHash) -> Result<bool>
Return whether the blob is owned by this store, excluding any configured read-through source. Snapshot builders use this to ensure a new native state owns its complete object closure.
Sourcefn has_tree_locally(&self, hash: &ContentHash) -> Result<bool>
fn has_tree_locally(&self, hash: &ContentHash) -> Result<bool>
Return whether the tree is owned by this store, excluding any configured read-through source.
Sourcefn open_tree(
&self,
tree_id: &ContentHash,
cursor: Option<&TreeResumeCursor>,
) -> Result<Option<TreeEntryReader<OpenedTreeBody>>>
fn open_tree( &self, tree_id: &ContentHash, cursor: Option<&TreeResumeCursor>, ) -> Result<Option<TreeEntryReader<OpenedTreeBody>>>
Open a streamable HTR4 tree body. Store backends use sequential verify: resume at ordinal > 0 is refused until the bytes are hashed.
fn get_state_attachment( &self, _state: &StateId, _id: &StateAttachmentId, ) -> Result<Option<StateAttachment>>
fn put_state_attachment( &self, _attachment: &StateAttachment, ) -> Result<StateAttachmentId>
fn list_state_attachments( &self, _state: &StateId, ) -> Result<Vec<StateAttachment>>
fn put_blob_bytes_with_hash( &self, data: &[u8], hash: ContentHash, ) -> Result<ContentHash>
Sourcefn get_tree_serialized(&self, hash: &ContentHash) -> Result<Option<Vec<u8>>>
fn get_tree_serialized(&self, hash: &ContentHash) -> Result<Option<Vec<u8>>>
Return the stored tree body for hash, without requiring HTR4.
This is a migration seam, not a runtime compatibility reader: callers
that need current tree semantics should use ObjectStore::get_tree.
Loose and packed backends must return the raw stored bytes so one-shot
migrations can canonicalize older encodings without a current-decoder
gate. Default impls that only have get_tree re-encode current trees.
fn put_tree_serialized( &self, data: &[u8], hash: ContentHash, ) -> Result<ContentHash>
fn put_state_serialized(&self, data: &[u8], id: StateId) -> Result<()>
fn put_action_serialized(&self, data: &[u8], id: ActionId) -> Result<()>
fn get_pack_object( &self, id: &PackObjectId, ) -> Result<Option<(ObjectType, Vec<u8>)>>
Sourcefn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()>
fn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()>
Bulk-write a batch of blobs as a single durable unit. The default
implementation falls back to per-blob writes; backends that
support packfiles (i.e. FsStore) override this to install one
packfile + index — two fsyncs total instead of N. Used by the
snapshot hot path so writing 1000 small files takes ~one fsync,
not 1000.
Blobs already present in the store are skipped on the way in (the caller would otherwise duplicate them in the pack).
Sourcefn put_snapshot_objects_packed(
&self,
blobs: Vec<(ContentHash, Vec<u8>)>,
tree: &Tree,
state: &State,
) -> Result<()>
fn put_snapshot_objects_packed( &self, blobs: Vec<(ContentHash, Vec<u8>)>, tree: &Tree, state: &State, ) -> Result<()>
Durably install a snapshot’s newly-authored immutable object closure as one storage batch. Pack-capable backends override this to share one pack installation across blobs, the root tree, and the state; other backends preserve the same ordering through their ordinary object methods.
Sourcefn put_snapshot_objects_and_attachments_packed(
&self,
blobs: Vec<(ContentHash, Vec<u8>)>,
tree: &Tree,
state: &State,
attachments: Vec<StateAttachment>,
) -> Result<()>
fn put_snapshot_objects_and_attachments_packed( &self, blobs: Vec<(ContentHash, Vec<u8>)>, tree: &Tree, state: &State, attachments: Vec<StateAttachment>, ) -> Result<()>
Snapshot closure variant that also durably installs immutable authored attachments. The separate method preserves the existing backend API; pack-capable stores override it to share the snapshot pack barrier.
fn install_pack( &self, pack_data: &[u8], index_data: &[u8], ) -> Result<Vec<PackObjectId>>
Sourcefn install_pack_streaming(
&self,
pack_path: &Path,
index_path: &Path,
) -> Result<Vec<PackObjectId>>
fn install_pack_streaming( &self, pack_path: &Path, index_path: &Path, ) -> Result<Vec<PackObjectId>>
Install a pack and its index from on-disk files
(typically produced by StreamingPackBuilder). The default
impl reads both files fully and delegates to install_pack,
so any backend that doesn’t override this still works (at the
cost of giving back the bounded-memory promise). Real fs-
backed stores override this to rename(2) both files into the
pack directory without ever loading them.
On success, the source files at pack_path/index_path may
have been moved or removed depending on the backend; callers
shouldn’t continue to rely on them.
Returns the ids of the installed objects — the same set
install_pack reports for the equivalent byte-buffer install,
so callers (e.g. native sync) read the installed ids off the
install result instead of tracking them out-of-band.
fn pack_objects(&self, delta_search: bool) -> Result<(u64, u64)>
fn prune_loose_objects(&self) -> Result<(u64, u64)>
Sourcefn discard_corrupt_clone_packs(&self) -> Result<usize>
fn discard_corrupt_clone_packs(&self) -> Result<usize>
Remove only pack/index pairs that fail checksum, index, or object-hash validation so a clone repair pull advertises their objects as missing.
fn begin_snapshot_write_batch(&self) -> Result<()>
fn flush_snapshot_write_batch(&self) -> Result<()>
fn abort_snapshot_write_batch(&self)
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".