use std::str;
use bitcode::{Decode, Encode};
use wbase::{
base32::{BASE32_LEN_U128, Base32Buf128, decode_u128, encode_u128},
crc::Crc32Hasher,
};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Encode, Decode)]
#[repr(u8)]
pub enum CprPhase {
#[default]
Rest = 0,
Prepare = 1,
InProgress = 2,
WaitFlush = 3,
Complete = 4,
}
impl CprPhase {
#[inline]
pub const fn next_phase(self) -> Self {
match self {
Self::Rest => Self::Prepare,
Self::Prepare => Self::InProgress,
Self::InProgress => Self::WaitFlush,
Self::WaitFlush => Self::Complete,
Self::Complete => Self::Rest,
}
}
#[inline]
pub const fn as_str(self) -> &'static str {
match self {
Self::Rest => "REST",
Self::Prepare => "PREPARE",
Self::InProgress => "IN_PROGRESS",
Self::WaitFlush => "WAIT_FLUSH",
Self::Complete => "COMPLETE",
}
}
#[inline]
pub const fn is_rest(self) -> bool {
matches!(self, Self::Rest)
}
#[inline]
pub const fn is_active(self) -> bool {
!self.is_rest()
}
}
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_base32_filename(prefix: &str, token: u128, ext: &str, suffix: &str) -> String {
let b32 = encode_u128(token);
let mut out = String::with_capacity(prefix.len() + b32.len() + ext.len() + suffix.len());
out.push_str(prefix);
out.push_str(b32.as_str());
out.push_str(ext);
out.push_str(suffix);
out
}
#[inline]
pub fn meta_filename(token: u128) -> String {
build_base32_filename(META_PREFIX, token, META_EXT, "")
}
#[inline]
pub fn meta_tmp_filename(token: u128) -> String {
build_base32_filename(META_PREFIX, token, META_EXT, TMP_EXT)
}
#[inline]
pub fn index_filename(token: u128) -> String {
build_base32_filename(INDEX_PREFIX, token, INDEX_EXT, "")
}
#[inline]
pub fn index_tmp_filename(token: u128) -> String {
build_base32_filename(INDEX_PREFIX, token, INDEX_EXT, TMP_EXT)
}
#[inline]
pub fn token_to_base32(token: u128) -> Base32Buf128 {
encode_u128(token)
}
#[inline]
pub fn parse_token(s: &str) -> Option<u128> {
let s = s.trim();
if s.len() == BASE32_LEN_U128 {
decode_u128(s)
} else {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
pub enum CheckpointType {
FoldOver,
Snapshot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, 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, 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, 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,
pub enable_revivification: bool,
pub enable_read_cache: bool,
pub read_cache_num_pages: usize,
pub range_index_dir: Option<String>,
pub next_key_id: u64,
}
#[derive(Debug, Clone, PartialEq, 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,
pub format_version: u32,
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);
match &s.range_index_dir {
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(&self) -> Vec<u8> {
bitcode::encode(self)
}
#[inline]
pub fn decode(bytes: &[u8]) -> Result<Self> {
bitcode::decode(bytes).map_err(Error::from)
}
}