use bitcode::{Decode, Encode};
use serde::{Deserialize, Serialize};
use wbase::crc::Crc32Hasher;
use crate::{Error, Result};
pub const FORMAT_VERSION: u32 = 2;
pub const INTEGRITY_FROM_VERSION: u32 = 2;
pub const META_PREFIX: &str = "checkpoint_";
pub const META_EXT: &str = ".meta";
pub const INDEX_PREFIX: &str = "index_";
pub const INDEX_EXT: &str = ".ckpt";
pub const TMP_EXT: &str = ".tmp";
#[inline]
fn build_filename(prefix: &str, token: u128, ext: &str, suffix: &str) -> String {
let mut itoa_buf = itoa::Buffer::new();
let s = itoa_buf.format(token);
let mut out = String::with_capacity(prefix.len() + s.len() + ext.len() + suffix.len());
out.push_str(prefix);
out.push_str(s);
out.push_str(ext);
out.push_str(suffix);
out
}
#[inline]
pub fn meta_filename(token: u128) -> String {
build_filename(META_PREFIX, token, META_EXT, "")
}
#[inline]
pub fn meta_tmp_filename(token: u128) -> String {
build_filename(META_PREFIX, token, META_EXT, TMP_EXT)
}
#[inline]
pub fn index_filename(token: u128) -> String {
build_filename(INDEX_PREFIX, token, INDEX_EXT, "")
}
#[inline]
pub fn index_tmp_filename(token: u128) -> String {
build_filename(INDEX_PREFIX, token, INDEX_EXT, TMP_EXT)
}
fn default_read_cache_pages() -> usize {
8
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Encode, Decode)]
pub enum CheckpointType {
FoldOver,
Snapshot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Encode, Decode)]
pub struct IndexMeta {
pub size: usize,
pub overflow_count: u64,
pub entry_count: usize,
}
impl IndexMeta {
pub const META_SIZE: usize = 24;
#[inline(always)]
pub const fn to_bytes(&self) -> [u8; Self::META_SIZE] {
let s = (self.size as u64).to_le_bytes();
let o = self.overflow_count.to_le_bytes();
let e = (self.entry_count as u64).to_le_bytes();
[
s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], o[0], o[1], o[2], o[3], o[4], o[5], o[6],
o[7], e[0], e[1], e[2], e[3], e[4], e[5], e[6], e[7],
]
}
#[inline(always)]
pub const fn from_bytes(bytes: [u8; Self::META_SIZE]) -> Self {
let size = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]) as usize;
let overflow_count = u64::from_le_bytes([
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
]);
let entry_count = u64::from_le_bytes([
bytes[16], bytes[17], bytes[18], bytes[19], bytes[20], bytes[21], bytes[22], bytes[23],
]) as usize;
Self {
size,
overflow_count,
entry_count,
}
}
#[inline(always)]
pub const fn decode_opt(src: &[u8]) -> Option<Self> {
if let Some(bytes) = src.first_chunk::<{ Self::META_SIZE }>() {
Some(Self::from_bytes(*bytes))
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Encode, Decode)]
pub struct HlogMeta {
pub begin_address: u64,
pub head_address: u64,
pub flushed_until_address: u64,
pub tail_address: u64,
}
impl HlogMeta {
pub const META_SIZE: usize = 32;
#[inline(always)]
pub const fn to_bytes(&self) -> [u8; Self::META_SIZE] {
let b = self.begin_address.to_le_bytes();
let h = self.head_address.to_le_bytes();
let f = self.flushed_until_address.to_le_bytes();
let t = self.tail_address.to_le_bytes();
[
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], h[0], h[1], h[2], h[3], h[4], h[5], h[6],
h[7], f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], t[0], t[1], t[2], t[3], t[4], t[5],
t[6], t[7],
]
}
#[inline(always)]
pub const fn from_bytes(bytes: [u8; Self::META_SIZE]) -> Self {
let begin_address = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]);
let head_address = u64::from_le_bytes([
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
]);
let flushed_until_address = u64::from_le_bytes([
bytes[16], bytes[17], bytes[18], bytes[19], bytes[20], bytes[21], bytes[22], bytes[23],
]);
let tail_address = u64::from_le_bytes([
bytes[24], bytes[25], bytes[26], bytes[27], bytes[28], bytes[29], bytes[30], bytes[31],
]);
Self {
begin_address,
head_address,
flushed_until_address,
tail_address,
}
}
#[inline(always)]
pub const fn decode_opt(src: &[u8]) -> Option<Self> {
if let Some(bytes) = src.first_chunk::<{ Self::META_SIZE }>() {
Some(Self::from_bytes(*bytes))
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)]
pub struct StoreMeta {
pub index_size: usize,
pub page_size: usize,
pub num_pages: usize,
pub mutable_fraction: f64,
pub max_sessions: usize,
#[serde(default)]
pub enable_revivification: bool,
#[serde(default)]
pub enable_read_cache: bool,
#[serde(default = "default_read_cache_pages")]
pub read_cache_num_pages: usize,
#[serde(default)]
pub range_index_dir: Option<String>,
#[serde(default)]
pub bftree_path: Option<String>,
#[serde(default)]
pub next_key_id: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)]
pub struct CheckpointMeta {
pub token: u128,
pub cp_type: CheckpointType,
pub index_meta: IndexMeta,
pub hlog_meta: HlogMeta,
pub store_meta: StoreMeta,
pub created_at: u64,
#[serde(default)]
pub format_version: u32,
#[serde(default)]
pub integrity_crc32: u32,
}
impl CheckpointMeta {
pub fn integrity_digest(&self) -> u32 {
let mut h = Crc32Hasher::new();
h.update_u32(self.format_version);
h.update(&self.token.to_le_bytes());
h.update(&[match self.cp_type {
CheckpointType::FoldOver => 0u8,
CheckpointType::Snapshot => 1u8,
}]);
let g = &self.hlog_meta;
h.update_u64(g.begin_address);
h.update_u64(g.head_address);
h.update_u64(g.flushed_until_address);
h.update_u64(g.tail_address);
h.update_u64(self.index_meta.size as u64);
h.update_u64(self.index_meta.overflow_count);
h.update_u64(self.index_meta.entry_count as u64);
let s = &self.store_meta;
h.update_u64(s.index_size as u64);
h.update_u64(s.page_size as u64);
h.update_u64(s.num_pages as u64);
h.update_u64(s.mutable_fraction.to_bits());
h.update_u64(s.max_sessions as u64);
h.update(&[
u8::from(s.enable_revivification),
u8::from(s.enable_read_cache),
]);
h.update_u64(s.read_cache_num_pages as u64);
for path in [&s.range_index_dir, &s.bftree_path] {
match path {
Some(p) => {
h.update_u64(p.len() as u64);
h.update(p.as_bytes());
}
None => h.update_u64(u64::MAX),
}
}
h.update_u64(s.next_key_id);
h.update_u64(self.created_at);
h.finalize()
}
#[inline]
pub fn seal(&mut self) {
self.integrity_crc32 = self.integrity_digest();
}
#[inline]
pub fn encode_bitcode(&self) -> Vec<u8> {
bitcode::encode(self)
}
#[inline]
pub fn decode_bitcode(bytes: &[u8]) -> Result<Self> {
bitcode::decode(bytes).map_err(Error::from)
}
#[inline]
pub fn encode_json(&self) -> Result<Vec<u8>> {
sonic_rs::to_vec_pretty(self).map_err(Error::from)
}
#[inline]
pub fn decode_json(bytes: &[u8]) -> Result<Self> {
sonic_rs::from_slice(bytes).map_err(Error::from)
}
pub fn decode_auto(bytes: &[u8]) -> Result<Self> {
let trimmed = bytes.trim_ascii_start();
if trimmed.first() == Some(&b'{') {
match Self::decode_json(bytes) {
Ok(meta) => Ok(meta),
Err(json_err) => Self::decode_bitcode(bytes).map_err(|_| json_err),
}
} else {
match Self::decode_bitcode(bytes) {
Ok(meta) => Ok(meta),
Err(bc_err) => Self::decode_json(bytes).map_err(|_| bc_err),
}
}
}
}