use std::{collections::HashMap, fmt, iter::FusedIterator, str::FromStr};
#[cfg(feature = "chunking")]
use crate::cdc::{ContentDefinedChunker, ContentDefinedChunkerConfig};
use crate::{
CreateOptions, FilesystemId,
chunker::{Chunker, FixedSizeChunker},
errors::InternalError,
};
pub const SQLITE_APPLICATION_ID: i32 = 0x6c626f78;
pub const SQLITE_USER_VERSION: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FormatVersion(u32);
impl FormatVersion {
pub const CURRENT: Self = Self(SQLITE_USER_VERSION);
}
impl From<u32> for FormatVersion {
fn from(value: u32) -> Self {
Self(value)
}
}
#[derive(Debug, Clone, Copy)]
pub enum Compression {
None,
Zstd { level: i32 },
}
impl Compression {
pub(crate) const ZSTD: Self = Self::Zstd { level: 1 };
const NONE_KEY: &str = "none";
const ZSTD_KEY: &str = "zstd";
fn key(&self) -> &'static str {
match self {
Self::None => Self::NONE_KEY,
Self::Zstd { .. } => Self::ZSTD_KEY,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Chunking {
FastCdc {
min_size: usize,
max_size: usize,
avg_size: usize,
},
Fixed {
size: usize,
},
}
impl Chunking {
pub(crate) fn new_cdc(block_size: usize) -> Self {
Self::FastCdc {
min_size: block_size / 4,
avg_size: block_size,
max_size: block_size * 4,
}
}
const FASTCDC_KEY: &str = "fastcdc";
const FIXED_KEY: &str = "fixed";
fn key(&self) -> &'static str {
match self {
Self::FastCdc { .. } => Self::FASTCDC_KEY,
Self::Fixed { .. } => Self::FIXED_KEY,
}
}
}
#[derive(Debug, Clone, Copy)]
enum SettingsKey {
Uuid,
Version,
Compression,
CompressionLevel,
Chunking,
MinChunkSize,
MaxChunkSize,
AvgChunkSize,
FixedChunkSize,
LogicalBlockSize,
MerkleBranchFactor,
SmallWriteThreshold,
MinWriteBufferSize,
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl fmt::Display for SettingsKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref())
}
}
impl AsRef<str> for SettingsKey {
fn as_ref(&self) -> &str {
use SettingsKey::*;
match self {
Uuid => "uuid",
Version => "version",
Compression => "compression",
CompressionLevel => "compression_level",
Chunking => "chunking",
MinChunkSize => "min_chunk_size",
MaxChunkSize => "max_chunk_size",
AvgChunkSize => "avg_chunk_size",
FixedChunkSize => "fixed_chunk_size",
LogicalBlockSize => "logical_block_size",
MerkleBranchFactor => "merkle_branch_factor",
SmallWriteThreshold => "small_write_threshold",
MinWriteBufferSize => "min_write_buffer_size",
}
}
}
#[derive(Debug, Clone)]
pub struct Settings {
pub uuid: FilesystemId,
pub version: FormatVersion,
pub compression: Compression,
pub chunking: Chunking,
pub logical_block_size: usize,
pub merkle_branch_factor: u32,
pub small_write_threshold: usize,
pub min_write_buffer_size: usize,
}
impl Default for Settings {
fn default() -> Self {
CreateOptions::default().try_into().expect(
"The default settings should always be valid, even when all features are disabled.",
)
}
}
impl Settings {
pub fn largest_block_size(&self) -> usize {
match self.chunking {
Chunking::FastCdc { max_size, .. } => max_size,
Chunking::Fixed { size } => size,
}
}
pub fn chunker(&self) -> Box<dyn Chunker> {
match self.chunking {
Chunking::Fixed { size } => Box::new(FixedSizeChunker::new(size as u32)),
#[cfg(feature = "chunking")]
Chunking::FastCdc {
min_size,
avg_size,
max_size,
} => Box::new(ContentDefinedChunker::new(ContentDefinedChunkerConfig {
min_size: min_size as u32,
avg_size: avg_size as u32,
max_size: max_size as u32,
})),
#[cfg(not(feature = "chunking"))]
Chunking::FastCdc { .. } => unimplemented!(),
}
}
}
#[derive(Debug)]
pub struct SettingsIter {
inner: std::vec::IntoIter<(String, String)>,
}
impl Iterator for SettingsIter {
type Item = (String, String);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
impl FusedIterator for SettingsIter {}
impl IntoIterator for &Settings {
type Item = (String, String);
type IntoIter = SettingsIter;
fn into_iter(self) -> Self::IntoIter {
let mut pairs = vec![
(SettingsKey::Uuid, self.uuid.to_string()),
(SettingsKey::Version, self.version.0.to_string()),
(SettingsKey::Compression, self.compression.key().to_string()),
(SettingsKey::Chunking, self.chunking.key().to_string()),
(
SettingsKey::LogicalBlockSize,
self.logical_block_size.to_string(),
),
(
SettingsKey::MerkleBranchFactor,
self.merkle_branch_factor.to_string(),
),
(
SettingsKey::SmallWriteThreshold,
self.small_write_threshold.to_string(),
),
(
SettingsKey::MinWriteBufferSize,
self.min_write_buffer_size.to_string(),
),
];
if let Compression::Zstd { level } = self.compression {
pairs.push((SettingsKey::CompressionLevel, level.to_string()));
}
match self.chunking {
Chunking::FastCdc {
min_size,
max_size,
avg_size,
} => {
pairs.push((SettingsKey::MinChunkSize, min_size.to_string()));
pairs.push((SettingsKey::MaxChunkSize, max_size.to_string()));
pairs.push((SettingsKey::AvgChunkSize, avg_size.to_string()));
}
Chunking::Fixed { size } => {
pairs.push((SettingsKey::FixedChunkSize, size.to_string()));
}
}
SettingsIter {
inner: pairs
.into_iter()
.map(|(key, value)| (key.to_string(), value))
.collect::<Vec<_>>()
.into_iter(),
}
}
}
#[derive(Debug)]
pub struct SettingsValidator {
map: HashMap<String, String>,
}
impl SettingsValidator {
fn get<V>(&self, key: impl AsRef<str>) -> crate::Result<V>
where
V: FromStr,
{
self.map
.get(key.as_ref())
.ok_or::<crate::Error>(InternalError::MalformedSettings.into())?
.parse::<V>()
.map_err(|_| InternalError::MalformedSettings.into())
}
}
impl TryFrom<SettingsValidator> for Settings {
type Error = crate::Error;
fn try_from(value: SettingsValidator) -> Result<Self, Self::Error> {
let compression = match value.get::<String>(SettingsKey::Compression)?.as_str() {
Compression::ZSTD_KEY if !cfg!(feature = "compression") => {
return Err(InternalError::CompressionDisabled.into());
}
Compression::ZSTD_KEY => Compression::Zstd {
level: value.get(SettingsKey::CompressionLevel)?,
},
Compression::NONE_KEY => Compression::None,
_ => return Err(InternalError::MalformedSettings.into()),
};
let chunking = match value.get::<String>(SettingsKey::Chunking)?.as_str() {
Chunking::FASTCDC_KEY if !cfg!(feature = "chunking") => {
return Err(InternalError::ChunkingDisabled.into());
}
Chunking::FASTCDC_KEY => Chunking::FastCdc {
min_size: value.get(SettingsKey::MinChunkSize)?,
max_size: value.get(SettingsKey::MaxChunkSize)?,
avg_size: value.get(SettingsKey::AvgChunkSize)?,
},
Chunking::FIXED_KEY => Chunking::Fixed {
size: value.get(SettingsKey::FixedChunkSize)?,
},
_ => return Err(InternalError::MalformedSettings.into()),
};
let version = FormatVersion::from(value.get::<u32>(SettingsKey::Version)?);
if version > FormatVersion::CURRENT {
return Err(crate::Error::UnsupportedFormatVersion);
}
Ok(Self {
uuid: FilesystemId::from_str(&value.get::<String>(SettingsKey::Uuid)?)?,
version,
compression,
chunking,
logical_block_size: value.get(SettingsKey::LogicalBlockSize)?,
merkle_branch_factor: value.get(SettingsKey::MerkleBranchFactor)?,
small_write_threshold: value.get(SettingsKey::SmallWriteThreshold)?,
min_write_buffer_size: value.get(SettingsKey::MinWriteBufferSize)?,
})
}
}
impl FromIterator<(String, String)> for SettingsValidator {
fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
Self {
map: iter.into_iter().collect::<HashMap<_, _>>(),
}
}
}
impl<'a> FromIterator<(&'a str, &'a str)> for SettingsValidator {
fn from_iter<T: IntoIterator<Item = (&'a str, &'a str)>>(iter: T) -> Self {
Self {
map: iter
.into_iter()
.map(|(key, value)| (key.to_string(), value.to_string()))
.collect::<HashMap<_, _>>(),
}
}
}