use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use nodedb_types::Namespace;
use nodedb_types::result::QueryResult;
use nodedb_types::value::Value;
use crate::error::LiteError;
use crate::storage::engine::{StorageEngine, WriteOp};
const WAL_LSN_KEY: &[u8] = b"__lite_wal_lsn__";
static WAL_LSN: AtomicU64 = AtomicU64::new(0);
static WAL_LSN_LOADED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
async fn next_lsn<S: StorageEngine>(storage: &Arc<S>) -> Result<u64, LiteError> {
if !WAL_LSN_LOADED.load(Ordering::Acquire) {
let persisted = storage
.get(Namespace::Meta, WAL_LSN_KEY)
.await
.map_err(|e| LiteError::Storage {
detail: e.to_string(),
})?
.map(|b| {
let arr: [u8; 8] = b.try_into().unwrap_or([0u8; 8]);
u64::from_le_bytes(arr)
})
.unwrap_or(0);
WAL_LSN.store(persisted, Ordering::Release);
WAL_LSN_LOADED.store(true, Ordering::Release);
}
let lsn = WAL_LSN.fetch_add(1, Ordering::AcqRel);
storage
.put(Namespace::Meta, WAL_LSN_KEY, &(lsn + 1).to_le_bytes())
.await
.map_err(|e| LiteError::Storage {
detail: e.to_string(),
})?;
Ok(lsn)
}
pub async fn handle_wal_append<S: StorageEngine>(
storage: &Arc<S>,
payload: &[u8],
) -> Result<QueryResult, LiteError> {
let lsn = next_lsn(storage).await?;
let mut key = Vec::with_capacity(12);
key.extend_from_slice(b"wal:");
key.extend_from_slice(&lsn.to_le_bytes());
storage
.batch_write(&[WriteOp::Put {
ns: Namespace::Meta,
key,
value: payload.to_vec(),
}])
.await?;
Ok(QueryResult {
columns: vec!["lsn".into()],
rows: vec![vec![Value::Integer(lsn as i64)]],
rows_affected: 0,
command: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::pagedb_storage::PagedbStorageMem;
use std::sync::Arc;
#[tokio::test]
async fn wal_append_assigns_monotonic_lsn() {
let storage = Arc::new(PagedbStorageMem::open_in_memory().await.unwrap());
WAL_LSN_LOADED.store(false, Ordering::SeqCst);
WAL_LSN.store(0, Ordering::SeqCst);
let r1 = handle_wal_append(&storage, b"op1").await.unwrap();
let r2 = handle_wal_append(&storage, b"op2").await.unwrap();
let lsn1 = match &r1.rows[0][0] {
Value::Integer(n) => *n,
_ => panic!("expected integer lsn"),
};
let lsn2 = match &r2.rows[0][0] {
Value::Integer(n) => *n,
_ => panic!("expected integer lsn"),
};
assert!(lsn2 > lsn1, "LSNs must be strictly increasing");
assert_eq!(lsn1 + 1, lsn2);
}
}