pub struct FileWriter { /* private fields */ }Expand description
A .verit file open for reading and writing, mutated by crash-safe
append-only commits.
append and the removal methods stage changes;
commit makes them durable and atomic. Nothing is visible to
a reader until a commit completes, and a crash mid-commit rolls the file back
to the previous generation — so a batch of appends and removals is a
transaction.
Records are addressed by id, not position: append returns the id it
assigned, and remove_id is the safe removal. Positional
remove exists but renumbers everything after it.
One writer at a time. Concurrent writers to the same path are undefined; mutual exclusion is the caller’s job. Concurrent readers need no coordination at all.
Implementations§
Source§impl FileWriter
impl FileWriter
Sourcepub fn create<P: AsRef<Path>>(path: P) -> Result<FileWriter>
pub fn create<P: AsRef<Path>>(path: P) -> Result<FileWriter>
Create a new file, truncating any existing one, and commit an empty
generation 1 so the path is immediately a valid .verit file.
Sourcepub fn create_checksummed<P: AsRef<Path>>(path: P) -> Result<FileWriter>
pub fn create_checksummed<P: AsRef<Path>>(path: P) -> Result<FileWriter>
Create a file that records a CRC-32 per record (OPT_RECORD_CRC).
The footer’s CRC proves a commit was not torn; it says nothing about the record bytes. For a file meant to be read years from now, this is the difference between detecting bit rot and trusting it. Readers without the feature are unaffected.
Sourcepub fn open<P: AsRef<Path>>(path: P) -> Result<FileWriter>
pub fn open<P: AsRef<Path>>(path: P) -> Result<FileWriter>
Open an existing file, recovering the newest valid commit (spec §7.2). Any uncommitted tail left by a crash is left in place until the next commit truncates it, so opening never destroys evidence.
Sourcepub fn open_locked<P: AsRef<Path>>(path: P) -> Result<FileWriter>
pub fn open_locked<P: AsRef<Path>>(path: P) -> Result<FileWriter>
Open a file and take an advisory exclusive lock on it, refusing if another locking writer already holds it.
The format allows one writer and many readers (§7.3), and deliberately
has no in-format locking scheme. This is the documented protocol
instead: a sibling <path>.lock, held until this writer is dropped.
It is advisory. It stops another FileWriter that also locks; it
cannot stop a process that ignores the convention. A lock left behind by
a killed process must be removed by hand — stealing it after a timeout
would turn a visible operational problem into a corrupted file.
Readers never need this, and never block.
Sourcepub fn open_or_create_locked<P: AsRef<Path>>(path: P) -> Result<FileWriter>
pub fn open_or_create_locked<P: AsRef<Path>>(path: P) -> Result<FileWriter>
open_locked, creating the file if it is absent.
Sourcepub fn open_or_create<P: AsRef<Path>>(path: P) -> Result<FileWriter>
pub fn open_or_create<P: AsRef<Path>>(path: P) -> Result<FileWriter>
Open the file if it exists, otherwise create it.
Sourcepub fn append(&mut self, schema: &Schema, value: &Value) -> Result<u64>
pub fn append(&mut self, schema: &Schema, value: &Value) -> Result<u64>
Stage value as a new record at the end, returning the record id it
was assigned. Takes effect on commit; the id is stable
from this moment and is what a consumer should checkpoint against.
Sourcepub fn append_message(&mut self, schema: &Schema, bytes: &[u8]) -> Result<u64>
pub fn append_message(&mut self, schema: &Schema, bytes: &[u8]) -> Result<u64>
Stage an already-encoded message with its writer schema.
Sourcepub fn append_self_describing(&mut self, bytes: &[u8]) -> Result<u64>
pub fn append_self_describing(&mut self, bytes: &[u8]) -> Result<u64>
Stage a self-describing (inline-schema) message, lifting its schema out of the message itself.
Sourcepub fn remove_id(&mut self, id: u64) -> Result<&mut Self>
pub fn remove_id(&mut self, id: u64) -> Result<&mut Self>
Stage the removal of the record with this id — the safe removal, since an id does not shift when its neighbours go away.
Removal unlinks; it does not erase (spec §8.2). The record’s bytes
stay in the file and remain fully recoverable with a hex editor until
compact rewrites it. Use purge_ids
when the data actually has to go.
Sourcepub fn remove_ids(&mut self, ids: &[u64]) -> usize
pub fn remove_ids(&mut self, ids: &[u64]) -> usize
Stage the removal of every listed id, returning how many were live. Unknown ids are ignored, so this is idempotent and safe to retry.
Sourcepub fn retain<F: FnMut(u64, u128) -> bool>(&mut self, keep: F) -> usize
pub fn retain<F: FnMut(u64, u128) -> bool>(&mut self, keep: F) -> usize
Keep only the records for which keep(id, schema_id) is true, returning
how many were removed. The bulk removal that cannot drift by one.
Deciding from a record’s contents needs its bytes, which this does not
hand you: read what you need with read_record
first, collect the doomed ids, then call
remove_ids.
Sourcepub fn purge_ids(&mut self, ids: &[u64]) -> Result<usize>
pub fn purge_ids(&mut self, ids: &[u64]) -> Result<usize>
Remove every listed id and erase it — one commit, then one compaction. Returns how many records were removed.
This is the call to reach for when the data genuinely has to go
(regulated or secret content), because plain removal only unlinks.
Batched on purpose: compaction rewrites the whole file, so doing it per
removal would be O(file) each time.
Erasure covers this file only. Backups, snapshots, and unallocated disk blocks from before the compaction are outside its reach.
pub fn is_empty(&self) -> bool
Sourcepub fn generation(&self) -> u64
pub fn generation(&self) -> u64
The generation of the last completed commit.
Sourcepub fn next_record_id(&self) -> u64
pub fn next_record_id(&self) -> u64
The id the next appended record will take.
pub fn path(&self) -> &Path
Sourcepub fn commit(&mut self) -> Result<u64>
pub fn commit(&mut self) -> Result<u64>
Make every staged change durable and atomic, per spec §7.1.
Truncate the uncommitted tail, append the new records, append the schema section and index, synchronise, then append the footer and synchronise again. The intermediate sync is load-bearing: it is what guarantees that a durable footer never points at records that never reached the disk.
Sourcepub fn compact(&mut self) -> Result<()>
pub fn compact(&mut self) -> Result<()>
Rewrite the file with only its live records, at generation 1 — the only operation that reclaims space, and the only one that actually erases removed records (spec §8.3).
Record ids are preserved, so a consumer’s checkpoint stays valid across a compaction; positions are not. The writer’s id counter is carried over too, so an id belonging to a purged record is never reissued.
Performed out of place: a fresh file is written and synchronised, then atomically renamed over the original, so a crash during compaction leaves the original intact and readable. Staged-but-uncommitted changes are committed first.
The rename is atomic, and on Unix the containing directory is synchronised afterwards so the rename itself survives a power loss. On other platforms that step is a no-op and the rename may be lost — the old file survives in that case, never a torn mix of the two.
Sourcepub fn read_record(&mut self, i: usize) -> Result<Vec<u8>>
pub fn read_record(&mut self, i: usize) -> Result<Vec<u8>>
Read record i’s bytes from disk. Committed records are read at their
offset; staged ones come straight from memory.