use std::time::Instant;
use crate::connection::SochConnection;
use crate::error::{ClientError, Result};
#[derive(Debug, Clone, PartialEq)]
pub enum RecoveryStatus {
Clean,
Recovered { replayed_entries: u64 },
Failed { reason: String },
Corrupted { details: String },
}
#[derive(Debug, Clone)]
pub struct WalVerificationResult {
pub is_valid: bool,
pub total_entries: u64,
pub valid_entries: u64,
pub corrupted_entries: u64,
pub last_valid_lsn: u64,
pub checksum_errors: Vec<ChecksumError>,
}
#[derive(Debug, Clone)]
pub struct ChecksumError {
pub lsn: u64,
pub expected: u64,
pub actual: u64,
pub entry_type: String,
}
pub struct RecoveryManager<'a> {
conn: &'a SochConnection,
}
impl<'a> RecoveryManager<'a> {
pub fn new(conn: &'a SochConnection) -> Self {
Self { conn }
}
pub fn needs_recovery(&self) -> bool {
false }
pub fn last_checkpoint_lsn(&self) -> u64 {
0 }
pub fn current_lsn(&self) -> u64 {
0 }
pub fn verify_wal(&self) -> Result<WalVerificationResult> {
Ok(WalVerificationResult {
is_valid: true,
total_entries: 0,
valid_entries: 0,
corrupted_entries: 0,
last_valid_lsn: 0,
checksum_errors: vec![],
})
}
pub fn recover(&self) -> Result<RecoveryStatus> {
let stats = self.conn.storage
.recover()
.map_err(|e| ClientError::Storage(e.to_string()))?;
if stats.transactions_recovered > 0 {
Ok(RecoveryStatus::Recovered {
replayed_entries: stats.writes_recovered as u64,
})
} else {
Ok(RecoveryStatus::Clean)
}
}
pub fn checkpoint(&self) -> Result<CheckpointResult> {
let start = Instant::now();
let lsn = self.conn.storage
.checkpoint()
.map_err(|e| ClientError::Storage(e.to_string()))?;
Ok(CheckpointResult {
checkpoint_lsn: lsn,
duration_ms: start.elapsed().as_millis() as u64,
})
}
pub fn truncate_wal(&self, _up_to_lsn: u64) -> Result<TruncateResult> {
Ok(TruncateResult {
up_to_lsn: _up_to_lsn,
bytes_freed: 0,
})
}
pub fn wal_stats(&self) -> WalStats {
WalStats {
total_size_bytes: 0,
active_size_bytes: 0,
archived_size_bytes: 0,
oldest_entry_lsn: 0,
newest_entry_lsn: 0,
entry_count: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct CheckpointResult {
pub checkpoint_lsn: u64,
pub duration_ms: u64,
}
#[derive(Debug, Clone)]
pub struct TruncateResult {
pub up_to_lsn: u64,
pub bytes_freed: u64,
}
#[derive(Debug, Clone)]
pub struct WalStats {
pub total_size_bytes: u64,
pub active_size_bytes: u64,
pub archived_size_bytes: u64,
pub oldest_entry_lsn: u64,
pub newest_entry_lsn: u64,
pub entry_count: u64,
}
impl SochConnection {
pub fn recovery(&self) -> RecoveryManager<'_> {
RecoveryManager::new(self)
}
pub fn needs_recovery(&self) -> bool {
self.recovery().needs_recovery()
}
pub fn recover(&self) -> Result<RecoveryStatus> {
self.recovery().recover()
}
pub fn force_checkpoint(&self) -> Result<CheckpointResult> {
self.recovery().checkpoint()
}
}
pub fn open_with_recovery(path: &str) -> Result<SochConnection> {
let conn = SochConnection::open(path)?;
match conn.recover()? {
RecoveryStatus::Clean => {
}
RecoveryStatus::Recovered {
replayed_entries: _,
} => {
}
RecoveryStatus::Failed { reason } => {
return Err(ClientError::Storage(format!("Recovery failed: {}", reason)));
}
RecoveryStatus::Corrupted { details } => {
return Err(ClientError::Storage(format!(
"Corruption detected: {}",
details
)));
}
}
Ok(conn)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recovery_status() {
let clean = RecoveryStatus::Clean;
assert_eq!(clean, RecoveryStatus::Clean);
let recovered = RecoveryStatus::Recovered {
replayed_entries: 100,
};
match recovered {
RecoveryStatus::Recovered { replayed_entries } => {
assert_eq!(replayed_entries, 100);
}
_ => panic!("Expected Recovered status"),
}
}
#[test]
fn test_recovery_manager() {
let conn = SochConnection::open("./test").unwrap();
let recovery = conn.recovery();
assert!(!recovery.needs_recovery());
}
#[test]
fn test_checkpoint() {
let conn = SochConnection::open("./test").unwrap();
let result = conn.force_checkpoint().unwrap();
let _ = result.checkpoint_lsn;
let _ = result.duration_ms;
}
#[test]
fn test_wal_verification() {
let conn = SochConnection::open("./test").unwrap();
let result = conn.recovery().verify_wal().unwrap();
assert!(result.is_valid);
assert_eq!(result.corrupted_entries, 0);
}
#[test]
fn test_wal_stats() {
let conn = SochConnection::open("./test").unwrap();
let stats = conn.recovery().wal_stats();
let _ = stats.total_size_bytes;
let _ = stats.entry_count;
}
}