use std::convert::TryInto;
use crate::db::TxId;
use crate::error::{GraphError, Result};
use crate::model::{EdgeId, NodeId};
use crate::pager::PageId;
const MAGIC: &[u8; 8] = b"GRPHITE\0";
const HEADER_REGION_SIZE: usize = 96;
const VERSION_MAJOR: u16 = 1;
const VERSION_MINOR: u16 = 2;
#[derive(Debug, Clone)]
pub struct Header {
pub page_size: u32,
pub next_node_id: NodeId,
pub next_edge_id: EdgeId,
pub free_page_head: Option<PageId>,
pub last_record_page: Option<PageId>,
pub last_committed_tx_id: TxId,
pub btree_index_page: Option<PageId>,
pub btree_index_size: u32,
pub property_index_root_page: Option<PageId>,
pub property_index_count: u32,
pub property_index_version: u16,
}
impl Header {
pub fn new(page_size: usize) -> Result<Self> {
let page_size_u32 = u32::try_from(page_size)
.map_err(|_| GraphError::InvalidArgument("page size exceeds u32::MAX".into()))?;
Ok(Self {
page_size: page_size_u32,
next_node_id: 1,
next_edge_id: 1,
free_page_head: None,
last_record_page: None,
last_committed_tx_id: 0,
btree_index_page: None,
btree_index_size: 0,
property_index_root_page: None,
property_index_count: 0,
property_index_version: 1,
})
}
pub fn read(data: &[u8]) -> Result<Option<Self>> {
if data.len() < HEADER_REGION_SIZE {
return Err(GraphError::Corruption(
"header page shorter than expected".into(),
));
}
if data[..MAGIC.len()].iter().all(|&b| b == 0) {
return Ok(None);
}
if &data[..MAGIC.len()] != MAGIC {
return Err(GraphError::Corruption(
"invalid graphite header magic".into(),
));
}
let major = u16::from_le_bytes([data[8], data[9]]);
let _minor = u16::from_le_bytes([data[10], data[11]]);
if major != VERSION_MAJOR {
return Err(GraphError::Corruption(format!(
"unsupported header major version {major} (expected {VERSION_MAJOR})"
)));
}
let page_size = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
let next_node_id = Self::read_u64(data, 16, 24)?;
let next_edge_id = Self::read_u64(data, 24, 32)?;
let free_page_head = u32::from_le_bytes([data[32], data[33], data[34], data[35]]);
let last_record_page = u32::from_le_bytes([data[36], data[37], data[38], data[39]]);
let last_committed_tx_id = Self::read_u64(data, 40, 48)?;
let btree_index_page = if data.len() >= 56 {
let page = u32::from_le_bytes([data[48], data[49], data[50], data[51]]);
if page == 0 {
None
} else {
Some(page)
}
} else {
None
};
let btree_index_size = if data.len() >= 56 {
u32::from_le_bytes([data[52], data[53], data[54], data[55]])
} else {
0
};
let property_index_root_page = if data.len() >= 64 {
let page = u32::from_le_bytes([data[56], data[57], data[58], data[59]]);
if page == 0 {
None
} else {
Some(page)
}
} else {
None
};
let property_index_count = if data.len() >= 64 {
u32::from_le_bytes([data[60], data[61], data[62], data[63]])
} else {
0
};
let property_index_version = if data.len() >= 66 {
u16::from_le_bytes([data[64], data[65]])
} else {
0
};
Ok(Some(Self {
page_size,
next_node_id,
next_edge_id,
free_page_head: if free_page_head == 0 {
None
} else {
Some(free_page_head)
},
last_record_page: if last_record_page == 0 {
None
} else {
Some(last_record_page)
},
last_committed_tx_id,
btree_index_page,
btree_index_size,
property_index_root_page,
property_index_count,
property_index_version,
}))
}
pub fn write(&self, data: &mut [u8]) -> Result<()> {
if data.len() < HEADER_REGION_SIZE {
return Err(GraphError::Corruption(
"header page shorter than expected".into(),
));
}
data.fill(0);
data[..MAGIC.len()].copy_from_slice(MAGIC);
data[8..10].copy_from_slice(&VERSION_MAJOR.to_le_bytes());
data[10..12].copy_from_slice(&VERSION_MINOR.to_le_bytes());
data[12..16].copy_from_slice(&self.page_size.to_le_bytes());
data[16..24].copy_from_slice(&self.next_node_id.to_le_bytes());
data[24..32].copy_from_slice(&self.next_edge_id.to_le_bytes());
data[32..36].copy_from_slice(&self.free_page_head.unwrap_or(0).to_le_bytes());
data[36..40].copy_from_slice(&self.last_record_page.unwrap_or(0).to_le_bytes());
data[40..48].copy_from_slice(&self.last_committed_tx_id.to_le_bytes());
data[48..52].copy_from_slice(&self.btree_index_page.unwrap_or(0).to_le_bytes());
data[52..56].copy_from_slice(&self.btree_index_size.to_le_bytes());
data[56..60].copy_from_slice(&self.property_index_root_page.unwrap_or(0).to_le_bytes());
data[60..64].copy_from_slice(&self.property_index_count.to_le_bytes());
data[64..66].copy_from_slice(&self.property_index_version.to_le_bytes());
Ok(())
}
fn read_u64(data: &[u8], start: usize, end: usize) -> Result<u64> {
let slice = data
.get(start..end)
.ok_or_else(|| GraphError::Corruption("header u64 field truncated".into()))?;
let bytes: [u8; 8] = slice
.try_into()
.map_err(|_| GraphError::Corruption("failed to parse u64 from header".into()))?;
Ok(u64::from_le_bytes(bytes))
}
}