Skip to main content

Writer

Struct Writer 

Source
pub struct Writer { /* private fields */ }
Expand description

A synchronous buffered writer for Acta v0.2 append sessions.

Writer::create exclusively creates its path and Writer::open resumes an existing complete file. They differ only in how the first the append state is established: create writes a prologue and schema frame and starts at sequence one, while open reconstructs the schema and continuation point from the file. From there both hold the same locked handle and enter the same append engine, which buffers appended nonempty RecordBatch values until a row or byte target causes a data frame to be published. flush publishes the final partial buffer, while sync and finish additionally request durability. A partial write or durability failure poisons the writer; dropping it performs no explicit I/O and may discard only uncommitted buffered rows.

Every write is an operating-system append, so a writer can only ever extend a file. No path in this type truncates, rewrites, or repairs committed bytes.

use std::sync::Arc;
use acta::{
    Array, Column, LogicalType, PrimitiveArray, RecordBatch, Schema, Writer, WriterOptions,
};

let schema = Schema::new(
    1,
    vec![Column::new(1, "value", LogicalType::Int64, false)],
    None,
);
let batch = RecordBatch::try_new(
    Arc::new(schema.clone()),
    vec![Array::Int64(PrimitiveArray::new(vec![1, 2, 3], None))],
    3,
)?;

let path = std::env::temp_dir().join("acta-writer-doc-example.acta");
let _ = std::fs::remove_file(&path);

let mut writer = Writer::create(&path, schema, WriterOptions::default())?;
writer.append(batch)?;
let summary = writer.finish()?;
assert_eq!(summary.rows_written(), 3);
assert_eq!(summary.blocks_written(), 1);

let _ = std::fs::remove_file(&path);

Implementations§

Source§

impl Writer

Source

pub fn create<P: AsRef<Path>>( path: P, schema: Schema, options: WriterOptions, ) -> Result<Self>

Exclusively create path, write its prologue and schema frame, and return a writer ready for data blocks.

The path must not already exist. This never opens, appends to, or overwrites an existing file, and it never removes one: only a file this call itself created can be cleaned up, and only when initializing it fails.

§Writer exclusion

The returned writer holds a cooperative exclusive lock on the file until it is finished or dropped. A second acta writer on the same file, in this process or another, fails immediately with ErrorKind::WriterLocked instead of waiting. Readers never take the lock and are never blocked by it: it is advisory on Unix, and on Windows it covers a single byte past the end of the addressable file rather than the data.

Its limits are worth stating plainly. Specification section 14 defers writer-locking protocols to a later format version, so this is a convention among acta writers rather than part of the format; no other Acta implementation participates in it, and no lock constrains a process that simply opens the path and writes. It is also unreliable on network filesystems, where advisory locks are emulated or absent. Targets that are neither Unix nor Windows have no lock primitive here at all, and both this method and Self::open refuse to construct a writer there.

Source

pub fn open<P: AsRef<Path>>(path: P, options: WriterOptions) -> Result<Self>

Open an existing complete Acta file and continue appending to it.

The file is authoritative. Its schema is reconstructed and returned by Self::schema, and options.row_ids must agree with the row-ID feature its prologue declares, because reopening can neither enable nor disable that feature. Codec, encoding, statistics, and block-size options apply to the blocks this session writes and leave existing blocks and file-level features untouched.

The path must already exist; this never creates one, and there is no open-or-create behavior.

§What opening validates

Opening performs structural and whole-frame validation in one linear pass: the prologue, the schema frame, and for every complete frame its prefix, header, trailer, and body CRC, along with sequence continuity and the implicit row-ID chain. It is not ValidationLevel::Full — no stream is decoded and no statistic is verified. Because every committed byte is checksummed, the cost is proportional to the size of the file, and a long series of small appends pays it once per session.

A file ending inside an unfinished frame is refused with ErrorKind::IncompleteTail rather than truncated, and damage to a frame that is present in full stays ErrorKind::Corruption. Recovery is a separate, explicit operation. No failure path here writes, truncates, or repairs a single byte.

§Writer exclusion

This takes the same cooperative exclusive lock as Self::create, with the same scope and the same limits; see that method. The lock is taken before any discovery, and the handle it is taken on is the handle this writer appends with, so there is no window between validating a path and writing to it.

use std::sync::Arc;
use acta::{
    Array, Column, LogicalType, PrimitiveArray, RecordBatch, Schema, Writer, WriterOptions,
};

let schema = Schema::new(
    1,
    vec![Column::new(1, "value", LogicalType::Int64, false)],
    None,
);
let path = std::env::temp_dir().join("acta-writer-open-doc-example.acta");
let _ = std::fs::remove_file(&path);
Writer::create(&path, schema, WriterOptions::default())?.finish()?;

let mut writer = Writer::open(&path, WriterOptions::default())?;
// The reconstructed schema is what new batches must match.
let schema = Arc::clone(writer.schema());
let batch = RecordBatch::try_new(
    schema,
    vec![Array::Int64(PrimitiveArray::new(vec![1, 2, 3], None))],
    3,
)?;
writer.append(batch)?;
let summary = writer.finish()?;
assert_eq!(summary.rows_written(), 3);
assert_eq!(summary.last_sequence(), Some(1));

let _ = std::fs::remove_file(&path);
Source

pub fn open_with_schema<P: AsRef<Path>>( path: P, expected_schema: &Schema, options: WriterOptions, ) -> Result<Self>

Open an existing complete Acta file for append and require its schema to equal expected_schema exactly.

Equality covers the schema ID, the column count and order, and every column’s ID, name, logical type and type parameters, and nullability, along with the primary-column selection. A difference in any of them fails with ErrorKind::SchemaMismatch before the file is walked and before a writer exists, so no byte is written.

This is a convenience over Self::open followed by comparing Self::schema; everything Self::open documents applies here.

Source

pub fn open_with_limits<P: AsRef<Path>>( path: P, limits: Limits, options: WriterOptions, ) -> Result<Self>

Open an existing complete Acta file for append under explicit Limits.

The bounds apply to the declared sizes this call reads out of the existing file, exactly as they do for Reader::open_with_limits. Without this, a file whose frames exceed Limits::default is readable but not appendable. The blocks this writer goes on to produce are still bounded by the format defaults, so it cannot emit a frame that an ordinary reader would refuse.

To combine a schema guard with custom limits, open with this method and compare Self::schema before appending.

Source

pub fn schema(&self) -> &Arc<Schema>

The schema every batch appended to this writer must match.

For a writer from Self::open this is the schema reconstructed from the file, which is authoritative. Clone it to build compatible RecordBatch values:

let schema = Arc::clone(writer.schema());
Source

pub fn append(&mut self, batch: RecordBatch) -> Result<()>

Append one nonempty batch to the bounded block buffer.

The batch schema must exactly match Self::schema, which is the schema passed to Self::create or, for a reopened writer, the one reconstructed from the file. Batch-shape and value errors are returned as crate::ErrorKind::InvalidArgument before anything is buffered, so a rejected batch leaves the writer exactly as it found it.

Reaching a block target publishes a frame from inside this call, so an append is not all-or-nothing against I/O. A failed publication poisons the writer and reports the failure, but blocks published earlier in the same call stay on disk and stay counted: compare WriteAccounting::total_rows across the call to see how much of the batch was accepted. A poisoned writer refuses further work, so the unaccepted rows are not retried.

Source

pub fn flush(&mut self) -> Result<()>

Publish buffered rows as a data frame and flush the operating-system file handle. This does not request durable storage.

Source

pub fn sync(&mut self) -> Result<()>

Flush and request durable storage for all bytes written so far.

Source

pub fn finish(self) -> Result<WriteSummary>

Flush, synchronize, consume the writer, and return write accounting.

This is the only way to end a writer without losing buffered rows, because Drop performs no I/O. A failure here therefore ends the file at its last published block and discards any rows still buffered along with the writer.

Source

pub fn accounting(&self) -> WriteAccounting

The current buffered, published, durable, and total data accounting.

Trait Implementations§

Source§

impl Debug for Writer

The sink is opaque, so the accounting is what a debug rendering can show.

Source§

fn fmt(&self, formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. 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<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.