Skip to main content

FileWriter

Struct FileWriter 

Source
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

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn open_or_create_locked<P: AsRef<Path>>(path: P) -> Result<FileWriter>

open_locked, creating the file if it is absent.

Source

pub fn is_locked(&self) -> bool

Whether this writer holds the advisory lock.

Source

pub fn open_or_create<P: AsRef<Path>>(path: P) -> Result<FileWriter>

Open the file if it exists, otherwise create it.

Source

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.

Source

pub fn append_message(&mut self, schema: &Schema, bytes: &[u8]) -> Result<u64>

Stage an already-encoded message with its writer schema.

Source

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.

Source

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.

Source

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.

Source

pub fn remove(&mut self, i: usize) -> Result<&mut Self>

Stage the removal of the record at position i.

Prefer remove_id: positions shift, so removing record i renumbers everything after it, and a loop over positions is an off-by-one waiting to happen. Same non-erasure caveat as remove_id.

Source

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.

Source

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.

Source

pub fn len(&self) -> usize

Live record count, including staged-but-uncommitted changes.

Source

pub fn is_empty(&self) -> bool

Source

pub fn pending(&self) -> usize

Number of staged records not yet committed.

Source

pub fn generation(&self) -> u64

The generation of the last completed commit.

Source

pub fn next_record_id(&self) -> u64

The id the next appended record will take.

Source

pub fn ids(&self) -> impl Iterator<Item = u64> + '_

The ids of every live record, in order.

Source

pub fn path(&self) -> &Path

Source

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.

Source

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.

Source

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.

Source

pub fn read_record_by_id(&mut self, id: u64) -> Result<Vec<u8>>

Read the bytes of the record with this id.

Source

pub fn image(&mut self) -> Result<Vec<u8>>

Read the whole file image back, for handing to FileView.

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<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

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.