use crate::{
descriptor_table::FileDescriptorTable,
segment::meta::{CompressionType, TableType},
serde::{Deserializable, Serializable},
BlockCache, DeserializeError, SerializeError, Tree,
};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use path_absolutize::Absolutize;
use std::{
io::{Read, Write},
path::{Path, PathBuf},
sync::Arc,
};
fn absolute_path<P: AsRef<Path>>(path: P) -> PathBuf {
path.as_ref()
.absolutize()
.expect("should be absolute path")
.into()
}
pub const CONFIG_HEADER_MAGIC: &[u8] = &[b'F', b'J', b'L', b'L', b'C', b'F', b'G', b'1'];
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum TreeType {
Standard,
}
impl From<TreeType> for u8 {
fn from(val: TreeType) -> Self {
match val {
TreeType::Standard => 0,
}
}
}
impl TryFrom<u8> for TreeType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Standard),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[allow(clippy::module_name_repetitions)]
pub struct PersistedConfig {
r#type: TreeType,
compression: CompressionType,
table_type: TableType,
pub block_size: u32,
pub level_count: u8,
}
const DEFAULT_FILE_FOLDER: &str = ".lsm.data";
impl Default for PersistedConfig {
fn default() -> Self {
Self {
block_size: 4_096,
level_count: 7,
r#type: TreeType::Standard,
compression: CompressionType::Lz4,
table_type: TableType::Block,
}
}
}
impl Serializable for PersistedConfig {
fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), SerializeError> {
writer.write_all(CONFIG_HEADER_MAGIC)?;
writer.write_u8(self.r#type.into())?;
writer.write_u8(self.compression.into())?;
writer.write_u8(self.table_type.into())?;
writer.write_u32::<BigEndian>(self.block_size)?;
writer.write_u8(self.level_count)?;
Ok(())
}
}
impl Deserializable for PersistedConfig {
fn deserialize<R: Read>(reader: &mut R) -> Result<Self, DeserializeError> {
let mut magic = [0u8; CONFIG_HEADER_MAGIC.len()];
reader.read_exact(&mut magic)?;
if magic != CONFIG_HEADER_MAGIC {
return Err(DeserializeError::InvalidHeader("Config"));
}
let tree_type = reader.read_u8()?;
let tree_type = TreeType::try_from(tree_type)
.map_err(|()| DeserializeError::InvalidTag(("TreeType", tree_type)))?;
let compression = reader.read_u8()?;
let compression = CompressionType::try_from(compression)
.map_err(|()| DeserializeError::InvalidTag(("CompressionType", compression)))?;
let table_type = reader.read_u8()?;
let table_type = TableType::try_from(table_type)
.map_err(|()| DeserializeError::InvalidTag(("TableType", table_type)))?;
let block_size = reader.read_u32::<BigEndian>()?;
let level_count = reader.read_u8()?;
Ok(Self {
r#type: tree_type,
compression,
table_type,
block_size,
level_count,
})
}
}
#[derive(Clone)]
pub struct Config {
#[doc(hidden)]
pub inner: PersistedConfig,
#[doc(hidden)]
pub path: PathBuf,
#[doc(hidden)]
pub block_cache: Arc<BlockCache>,
#[doc(hidden)]
pub descriptor_table: Arc<FileDescriptorTable>,
#[allow(clippy::doc_markdown)]
#[doc(hidden)]
pub level_ratio: u8,
}
impl Default for Config {
fn default() -> Self {
Self {
path: absolute_path(DEFAULT_FILE_FOLDER),
block_cache: Arc::new(BlockCache::with_capacity_bytes(8 * 1_024 * 1_024)),
descriptor_table: Arc::new(FileDescriptorTable::new(960, 4)),
inner: PersistedConfig::default(),
level_ratio: 8,
}
}
}
impl Config {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
let inner = PersistedConfig::default();
Self {
inner,
path: absolute_path(path),
..Default::default()
}
}
#[must_use]
pub fn level_count(mut self, n: u8) -> Self {
assert!(n > 0);
self.inner.level_count = n;
self
}
#[must_use]
pub fn level_ratio(mut self, n: u8) -> Self {
assert!(n > 1);
self.level_ratio = n;
self
}
#[must_use]
pub fn block_size(mut self, block_size: u32) -> Self {
assert!(block_size >= 1_024);
self.inner.block_size = block_size;
self
}
#[must_use]
pub fn block_cache(mut self, block_cache: Arc<BlockCache>) -> Self {
self.block_cache = block_cache;
self
}
#[must_use]
#[doc(hidden)]
pub fn descriptor_table(mut self, descriptor_table: Arc<FileDescriptorTable>) -> Self {
self.descriptor_table = descriptor_table;
self
}
pub fn open(self) -> crate::Result<Tree> {
Tree::open(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
use test_log::test;
#[test]
fn tree_config_raw() -> crate::Result<()> {
let config = PersistedConfig {
r#type: TreeType::Standard,
compression: CompressionType::Lz4,
table_type: TableType::Block,
block_size: 4_096,
level_count: 7,
};
let mut bytes = vec![];
config.serialize(&mut bytes)?;
#[rustfmt::skip]
let raw = &[
b'F', b'J', b'L', b'L', b'C', b'F', b'G', b'1',
0,
1,
0,
0, 0, 0x10, 0x00,
7,
];
assert_eq!(bytes, raw);
Ok(())
}
#[test]
fn tree_config_serde_round_trip() -> crate::Result<()> {
let config = PersistedConfig {
r#type: TreeType::Standard,
compression: CompressionType::Lz4,
table_type: TableType::Block,
block_size: 4_096,
level_count: 7,
};
let mut bytes = vec![];
config.serialize(&mut bytes)?;
let mut cursor = Cursor::new(bytes);
let config_copy = PersistedConfig::deserialize(&mut cursor)?;
assert_eq!(config, config_copy);
Ok(())
}
}