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
impl Writer
Sourcepub fn create<P: AsRef<Path>>(
path: P,
schema: Schema,
options: WriterOptions,
) -> Result<Self>
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.
Sourcepub fn open<P: AsRef<Path>>(path: P, options: WriterOptions) -> Result<Self>
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);Sourcepub fn open_with_schema<P: AsRef<Path>>(
path: P,
expected_schema: &Schema,
options: WriterOptions,
) -> Result<Self>
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.
Sourcepub fn open_with_limits<P: AsRef<Path>>(
path: P,
limits: Limits,
options: WriterOptions,
) -> Result<Self>
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.
Sourcepub fn schema(&self) -> &Arc<Schema> ⓘ
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());Sourcepub fn append(&mut self, batch: RecordBatch) -> Result<()>
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.
Sourcepub fn flush(&mut self) -> Result<()>
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.
Sourcepub fn sync(&mut self) -> Result<()>
pub fn sync(&mut self) -> Result<()>
Flush and request durable storage for all bytes written so far.
Sourcepub fn finish(self) -> Result<WriteSummary>
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.
Sourcepub fn accounting(&self) -> WriteAccounting
pub fn accounting(&self) -> WriteAccounting
The current buffered, published, durable, and total data accounting.