Implements the foundation of tidalDB's data pipeline: **Phase 1 – Schema primitives** - EntityId newtype (u64, big-endian ordering) - SignalTypeDefinition with pre-computed decay λ, deduped/sorted windows - SchemaBuilder with full constraint validation (duplicates, identifiers, half-life, windows, velocity) - LumenError wrapping all subsystems with required From impls **Phase 2 – Write-Ahead Log** - Length-prefixed, BLAKE3-protected entry format - Group-commit writer (batch up to 100 events / 10 ms) - Double-buffered content-hash deduplication - Checkpoint, truncation, and crash-recovery with full replay - Integration, property, and UAT tests (incl. 5,500-event deterministic UAT) - Proptest coverage scaled to 10 000 events/run (was ≤500) to meet acceptance criterion; cases reduced 100→10 to keep runtime comparable **Phase 3 – Storage engine** - StorageEngine trait (get/put/delete/scan/batch/flush) - Key encoding: [EntityId][0x00][Tag][suffix] with ordering/prefix helpers - InMemoryBackend (BTreeMap + RwLock) - FjallStorage with three isolated keyspaces and atomic batch helper - Property tests for key ordering and round-trip correctness Also adds planning docs for phases 4-5, research docs, architecture overview, and roadmap updates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
122 lines
3.3 KiB
Rust
122 lines
3.3 KiB
Rust
use std::fmt;
|
|
|
|
/// Errors originating from WAL operations.
|
|
///
|
|
/// Covers I/O failures, data corruption detected during recovery,
|
|
/// and lifecycle violations (e.g., appending after shutdown).
|
|
#[derive(Debug)]
|
|
pub enum WalError {
|
|
/// Underlying filesystem I/O failure.
|
|
Io(std::io::Error),
|
|
/// Data corruption detected (BLAKE3 mismatch, invalid magic, etc.).
|
|
Corruption { message: String },
|
|
/// Current segment is full; internal signal to trigger rotation.
|
|
SegmentFull,
|
|
/// Attempted append after WAL has been shut down.
|
|
Closed,
|
|
/// Channel send to writer thread failed (writer thread panicked or exited).
|
|
SendFailed,
|
|
/// Writer thread join failed during shutdown.
|
|
ShutdownFailed,
|
|
}
|
|
|
|
impl fmt::Display for WalError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Io(source) => write!(f, "WAL I/O error: {source}"),
|
|
Self::Corruption { message } => write!(f, "WAL corruption: {message}"),
|
|
Self::SegmentFull => f.write_str("WAL segment full"),
|
|
Self::Closed => f.write_str("WAL closed"),
|
|
Self::SendFailed => f.write_str("WAL channel send failed"),
|
|
Self::ShutdownFailed => f.write_str("WAL shutdown failed"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for WalError {
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
match self {
|
|
Self::Io(source) => Some(source),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<std::io::Error> for WalError {
|
|
fn from(e: std::io::Error) -> Self {
|
|
Self::Io(e)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn display_io() {
|
|
let e = WalError::Io(std::io::Error::new(
|
|
std::io::ErrorKind::NotFound,
|
|
"file not found",
|
|
));
|
|
assert!(e.to_string().contains("I/O error"));
|
|
assert!(e.to_string().contains("file not found"));
|
|
}
|
|
|
|
#[test]
|
|
fn display_corruption() {
|
|
let e = WalError::Corruption {
|
|
message: "bad checksum".into(),
|
|
};
|
|
assert_eq!(e.to_string(), "WAL corruption: bad checksum");
|
|
}
|
|
|
|
#[test]
|
|
fn display_segment_full() {
|
|
assert_eq!(WalError::SegmentFull.to_string(), "WAL segment full");
|
|
}
|
|
|
|
#[test]
|
|
fn display_closed() {
|
|
assert_eq!(WalError::Closed.to_string(), "WAL closed");
|
|
}
|
|
|
|
#[test]
|
|
fn display_send_failed() {
|
|
assert_eq!(WalError::SendFailed.to_string(), "WAL channel send failed");
|
|
}
|
|
|
|
#[test]
|
|
fn display_shutdown_failed() {
|
|
assert_eq!(WalError::ShutdownFailed.to_string(), "WAL shutdown failed");
|
|
}
|
|
|
|
#[test]
|
|
fn from_io_error() {
|
|
let io_err = std::io::Error::new(std::io::ErrorKind::Other, "disk full");
|
|
let wal_err: WalError = io_err.into();
|
|
assert!(matches!(wal_err, WalError::Io(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn source_io() {
|
|
use std::error::Error;
|
|
let e = WalError::Io(std::io::Error::new(std::io::ErrorKind::Other, "test"));
|
|
assert!(e.source().is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn source_corruption_is_none() {
|
|
use std::error::Error;
|
|
let e = WalError::Corruption {
|
|
message: "test".into(),
|
|
};
|
|
assert!(e.source().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn source_closed_is_none() {
|
|
use std::error::Error;
|
|
assert!(WalError::Closed.source().is_none());
|
|
}
|
|
}
|