use noxu_util::Vlsn;
const DIRTY_BIT: u32 = 0x80000000;
const CLEAR_DIRTY_BIT: u32 = !DIRTY_BIT;
const FETCHED_COLD_BIT: u32 = 0x40000000;
#[derive(Debug, Clone)]
pub struct Ln {
data: Option<Vec<u8>>,
flags: u32,
vlsn: Vlsn,
memory_size: usize,
}
impl Ln {
pub fn new(data: Option<Vec<u8>>) -> Self {
let memory_size = Self::compute_memory_size(&data);
let mut ln = Ln { data, flags: 0, vlsn: Vlsn::new(0), memory_size };
ln.set_dirty();
ln
}
pub fn from_bytes(data: &[u8]) -> Self {
Self::new(Some(data.to_vec()))
}
pub fn new_deleted() -> Self {
Self::new(None)
}
pub fn get_data(&self) -> Option<&[u8]> {
self.data.as_deref()
}
pub fn set_data(&mut self, data: Option<Vec<u8>>) {
self.memory_size = Self::compute_memory_size(&data);
self.data = data;
self.set_dirty();
}
pub fn is_deleted(&self) -> bool {
self.data.is_none()
}
pub fn is_dirty(&self) -> bool {
self.flags & DIRTY_BIT != 0
}
pub fn set_dirty(&mut self) {
self.flags |= DIRTY_BIT;
}
pub fn clear_dirty(&mut self) {
self.flags &= CLEAR_DIRTY_BIT;
}
pub fn is_fetched_cold(&self) -> bool {
self.flags & FETCHED_COLD_BIT != 0
}
pub fn set_fetched_cold(&mut self) {
self.flags |= FETCHED_COLD_BIT;
}
pub fn get_vlsn(&self) -> Vlsn {
self.vlsn
}
pub fn set_vlsn(&mut self, vlsn: Vlsn) {
self.vlsn = vlsn;
}
pub fn get_memory_size(&self) -> usize {
self.memory_size
}
pub fn get_memory_size_included_by_parent(&self) -> usize {
self.memory_size
}
fn compute_memory_size(data: &Option<Vec<u8>>) -> usize {
let base = std::mem::size_of::<Ln>();
match data {
Some(d) => base + d.len(),
None => base,
}
}
pub fn log_size(&self) -> usize {
let mut size = 1; if let Some(ref d) = self.data {
size += 4 + d.len(); }
size += 8; size
}
pub fn write_to_log(&self, buf: &mut Vec<u8>) {
if self.is_deleted() {
buf.push(1); } else {
buf.push(0);
let data = self.data.as_ref().unwrap();
buf.extend_from_slice(&(data.len() as u32).to_be_bytes());
buf.extend_from_slice(data);
}
buf.extend_from_slice(&self.vlsn.sequence().to_be_bytes());
}
pub fn read_from_log(buf: &[u8]) -> std::io::Result<Self> {
use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Cursor, Read};
let mut cursor = Cursor::new(buf);
let deleted_flag = cursor.read_u8()?;
let data = if deleted_flag == 0 {
let len = cursor.read_u32::<BigEndian>()? as usize;
let mut data = vec![0u8; len];
cursor.read_exact(&mut data)?;
Some(data)
} else {
None
};
let vlsn_seq = cursor.read_i64::<BigEndian>()?;
let mut ln =
Ln { data, flags: 0, vlsn: Vlsn::new(vlsn_seq), memory_size: 0 };
ln.memory_size = Ln::compute_memory_size(&ln.data);
Ok(ln)
}
}
impl std::fmt::Display for Ln {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_deleted() {
write!(f, "<ln deleted>")
} else {
write!(
f,
"<ln data_len={}>",
self.data.as_ref().map_or(0, |d| d.len())
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_ln() {
let data = b"hello world".to_vec();
let ln = Ln::new(Some(data.clone()));
assert!(!ln.is_deleted());
assert_eq!(ln.get_data(), Some(data.as_slice()));
assert!(ln.is_dirty());
assert!(!ln.is_fetched_cold());
}
#[test]
fn test_deleted_ln() {
let ln = Ln::new_deleted();
assert!(ln.is_deleted());
assert_eq!(ln.get_data(), None);
assert!(ln.is_dirty());
}
#[test]
fn test_dirty_flag() {
let mut ln = Ln::new(Some(b"test".to_vec()));
assert!(ln.is_dirty());
ln.clear_dirty();
assert!(!ln.is_dirty());
ln.set_dirty();
assert!(ln.is_dirty());
}
#[test]
fn test_set_data() {
let mut ln = Ln::new(Some(b"original".to_vec()));
ln.clear_dirty();
let new_data = b"modified".to_vec();
ln.set_data(Some(new_data.clone()));
assert_eq!(ln.get_data(), Some(new_data.as_slice()));
assert!(ln.is_dirty());
}
#[test]
fn test_serialization_roundtrip() {
let data = b"test data for serialization".to_vec();
let mut ln = Ln::new(Some(data.clone()));
ln.set_vlsn(Vlsn::new(42));
let mut buf = Vec::new();
ln.write_to_log(&mut buf);
let ln2 = Ln::read_from_log(&buf).unwrap();
assert_eq!(ln2.get_data(), Some(data.as_slice()));
assert_eq!(ln2.get_vlsn().sequence(), 42);
assert!(!ln2.is_dirty()); }
#[test]
fn test_serialization_roundtrip_deleted() {
let mut ln = Ln::new_deleted();
ln.set_vlsn(Vlsn::new(99));
let mut buf = Vec::new();
ln.write_to_log(&mut buf);
let ln2 = Ln::read_from_log(&buf).unwrap();
assert!(ln2.is_deleted());
assert_eq!(ln2.get_data(), None);
assert_eq!(ln2.get_vlsn().sequence(), 99);
}
#[test]
fn test_vlsn() {
let mut ln = Ln::new(Some(b"test".to_vec()));
assert_eq!(ln.get_vlsn().sequence(), 0);
ln.set_vlsn(Vlsn::new(12345));
assert_eq!(ln.get_vlsn().sequence(), 12345);
}
#[test]
fn test_memory_size() {
let small_ln = Ln::new(Some(b"abc".to_vec()));
let large_ln = Ln::new(Some(vec![0u8; 1000]));
let deleted_ln = Ln::new_deleted();
assert!(small_ln.get_memory_size() > 0);
assert!(large_ln.get_memory_size() > small_ln.get_memory_size());
assert!(deleted_ln.get_memory_size() > 0);
assert_eq!(
small_ln.get_memory_size(),
small_ln.get_memory_size_included_by_parent()
);
}
#[test]
fn test_fetched_cold() {
let mut ln = Ln::new(Some(b"test".to_vec()));
assert!(!ln.is_fetched_cold());
ln.set_fetched_cold();
assert!(ln.is_fetched_cold());
}
#[test]
fn test_from_bytes() {
let data = b"hello";
let ln = Ln::from_bytes(data);
assert_eq!(ln.get_data(), Some(data.as_slice()));
assert!(!ln.is_deleted());
}
#[test]
fn test_log_size() {
let ln = Ln::new(Some(b"test".to_vec()));
let size = ln.log_size();
assert_eq!(size, 1 + 4 + 4 + 8);
let deleted_ln = Ln::new_deleted();
let deleted_size = deleted_ln.log_size();
assert_eq!(deleted_size, 1 + 8);
}
#[test]
fn test_display() {
let ln = Ln::new(Some(b"hello".to_vec()));
let s = format!("{}", ln);
assert!(s.contains("data_len=5"));
let deleted = Ln::new_deleted();
let s = format!("{}", deleted);
assert!(s.contains("deleted"));
}
}