1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Acta v0.2 file validation and metadata reading.
//!
//! [`validate`] checks a file's framing, schema descriptors, and block
//! metadata. [`validate_with_options`] can additionally decode every complete
//! block and verify logical values and optional statistics. [`Reader`] exposes
//! that same information as an open-time snapshot of the schema and the
//! committed blocks. Both walk the file through one shared parser, so they
//! agree about which files are well formed.
//!
//! Logical block decoding is available through [`Reader::read_block`], which
//! decodes one whole block, and the lazy sequential [`Reader::scan`] API, which
//! can restrict a read to a projection of the columns and to a half-open
//! [`PrimaryRange`] over the primary column, pruning blocks by their stored
//! bounds before reading them. A snapshot [`Scan`] is immutable: it decodes
//! only the blocks the reader held when it was created, so later appends
//! cannot enter it. To follow an append-only file as it grows,
//! [`Reader::refresh`] extends the snapshot in place with frames committed
//! since it was opened, and [`Reader::tail`] returns a [`Tail`] that polls the
//! path for newly committed frames synchronously, one block per poll, without
//! sleeping or blocking. A tail never exposes a partial frame, never infers a
//! global primary ordering, and stops cleanly by being dropped. [`Writer`]
//! creates deterministic plain/raw v0.2 files by default, buffers appended
//! batches into blocks bounded by a row and a byte target, and can explicitly
//! select Zstandard when the default `zstd` feature is enabled. Writer-side
//! transforms, fixed policies, deterministic adaptive encoding, and optional
//! block-local min/max statistics are available through additive writer
//! options; their defaults leave the plain/raw output unchanged. Statistics
//! are written under [`WriterStatistics`], which is generation only: nothing
//! reads them for pruning, since [`PrimaryRange`] prunes on the mandatory
//! primary bounds in the block header. [`Writer::open`] reconstructs a
//! complete existing file and resumes the same append engine that
//! [`Writer::create`] enters, exposing the reconstructed schema through
//! [`Writer::schema`]; [`Writer::open_with_schema`] adds an exact schema guard
//! and [`Writer::open_with_limits`] reads the existing file under explicit
//! [`Limits`]. Both entry points hold a cooperative exclusive writer lock that
//! never blocks readers. Incomplete tails require explicit recovery and are
//! not silently truncated. [`inspect_recovery`] is a read-only snapshot, while
//! [`repair_incomplete_tail`] is a destructive operation narrowly limited to a
//! structurally verified incomplete final data frame.
//!
//! ```
//! let report = acta::validate("spec/v0.2/fixtures/minimal/minimal.acta")?;
//!
//! assert_eq!(report.format_version(), (0, 2));
//! assert_eq!(report.frame_count(), 2);
//! assert!(!report.incomplete_tail());
//! # Ok::<(), acta::Error>(())
//! ```
//!
//! Explicit recovery is a read-only inspection followed by an intentional,
//! narrowly bounded repair:
//!
//! ```no_run
//! let path = std::path::Path::new("ticks.acta");
//! let plan = acta::inspect_recovery(path)?;
//! if plan.requires_repair() {
//! let summary = acta::repair_incomplete_tail(path)?;
//! println!("removed {} bytes", summary.bytes_removed());
//! }
//! # Ok::<(), acta::Error>(())
//! ```
//!
//! Full validation is opt-in because it decodes every complete block and
//! verifies optional statistics, while structural validation only walks the
//! metadata needed to establish a safe snapshot.
pub use ;
pub use RecordBatch;
pub use ;
pub use Limits;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
use Path;
/// Validate the Acta v0.2 structure of `path` under the default [`Limits`].
///
/// This is the inexpensive metadata-only level. It covers the prologue, frame
/// envelopes and CRCs, the schema frame's column descriptors, and each data
/// frame's block header. It does not decode logical streams or inspect claims
/// that require values, such as `TS_SORTED` ordering and optional statistics.
/// Use [`validate_with_options`] with [`ValidationLevel::Full`] for that more
/// expensive pass.
///
/// A complete file returns a report whose [`incomplete_tail`] method is false.
/// If the file ends inside a frame after its schema frame, validation succeeds
/// with that method returning true and [`last_good_offset`] identifying the
/// byte after the last complete frame. A complete but corrupt frame returns an
/// error, as does a file that ends before its schema frame is complete.
///
/// [`incomplete_tail`]: ValidationReport::incomplete_tail
/// [`last_good_offset`]: ValidationReport::last_good_offset
/// Validate the Acta v0.2 framing of `path` under caller-supplied `limits`.
///
/// ```
/// let limits = acta::Limits::default().with_max_frame_payload_length(8);
/// let error = acta::validate_with_limits(
/// "spec/v0.2/fixtures/minimal/minimal.acta",
/// limits,
/// )
/// .unwrap_err();
///
/// assert_eq!(error.kind(), acta::ErrorKind::ResourceLimit);
/// ```
/// Validate an Acta file with an explicit level and resource limits.
///
/// Structural validation reads metadata and checks frame integrity without
/// materializing logical columns. Full validation has the cost of decoding
/// every complete block, including all stream transforms and codecs, and also
/// verifies optional min/max statistics against the decoded values.
///
/// The two levels report identical [`ValidationReport`] values for any file
/// both accept. They do not accept exactly the same files: full validation
/// builds a [`Reader`] snapshot and so additionally enforces the limits that
/// only apply to a snapshot, notably [`Limits::max_blocks`] and the per-block
/// decode limits. A file with more blocks than that limit passes structural
/// validation and fails full validation with
/// [`ErrorKind::ResourceLimit`]; raise the
/// limit to validate it fully.