#[cfg(feature = "config-toml")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "config-toml")]
use std::fs;
#[cfg(feature = "config-toml")]
use std::path::Path;
use crate::storage::index_persistence::PersistenceConfig;
use crate::storage::version::AnchorConfig;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "config-toml", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "config-toml", serde(default))]
#[non_exhaustive]
pub struct WalConfig {
pub num_stripes: usize,
pub stripe_capacity: usize,
pub write_buffer_size: usize,
pub segment_size: usize,
pub flush_interval_ms: u64,
pub wal_dir: std::path::PathBuf,
pub segments_to_retain: usize,
pub durability_mode: crate::storage::wal::DurabilityMode,
}
impl Default for WalConfig {
fn default() -> Self {
Self {
num_stripes: 16,
stripe_capacity: 1024,
write_buffer_size: 64 * 1024, segment_size: 64 * 1024 * 1024, flush_interval_ms: 10,
wal_dir: std::path::PathBuf::from("aletheiadb/wal"),
segments_to_retain: 10,
durability_mode: crate::storage::wal::DurabilityMode::group_commit_default(),
}
}
}
#[must_use = "builders do nothing unless you call build()"]
#[derive(Debug)]
pub struct WalConfigBuilder {
config: WalConfig,
}
impl WalConfigBuilder {
pub fn new() -> Self {
Self {
config: WalConfig::default(),
}
}
pub fn with_validated(
self,
num_stripes: usize,
stripe_capacity: usize,
write_buffer_size: usize,
segment_size: usize,
segments_to_retain: usize,
flush_interval_ms: u64,
) -> Result<Self, ConfigError> {
self.num_stripes(num_stripes)?
.stripe_capacity(stripe_capacity)?
.write_buffer_size(write_buffer_size)?
.segment_size(segment_size)?
.segments_to_retain(segments_to_retain)?
.flush_interval_ms(flush_interval_ms)
}
pub fn num_stripes(mut self, num_stripes: usize) -> Result<Self, ConfigError> {
if num_stripes == 0 {
return Err(ConfigError::InvalidValue(
"num_stripes must be greater than 0".into(),
));
}
let rounded = num_stripes.next_power_of_two();
if rounded != num_stripes {
#[cfg(feature = "observability")]
tracing::warn!(
original = num_stripes,
rounded = rounded,
"num_stripes rounded to next power of 2"
);
}
self.config.num_stripes = rounded;
Ok(self)
}
pub fn stripe_capacity(mut self, capacity: usize) -> Result<Self, ConfigError> {
if capacity == 0 {
return Err(ConfigError::InvalidValue(
"stripe_capacity must be greater than 0".into(),
));
}
self.config.stripe_capacity = capacity;
Ok(self)
}
pub fn write_buffer_size(mut self, size: usize) -> Result<Self, ConfigError> {
if size == 0 {
return Err(ConfigError::InvalidValue(
"write_buffer_size must be greater than 0".into(),
));
}
self.config.write_buffer_size = size;
Ok(self)
}
pub fn segment_size(mut self, size: usize) -> Result<Self, ConfigError> {
const MIN_SEGMENT_SIZE: usize = 512; if size == 0 {
return Err(ConfigError::InvalidValue(
"segment_size must be greater than 0".into(),
));
}
if size < MIN_SEGMENT_SIZE {
return Err(ConfigError::InvalidValue(format!(
"segment_size must be at least {} bytes, got {}",
MIN_SEGMENT_SIZE, size
)));
}
self.config.segment_size = size;
Ok(self)
}
pub fn flush_interval_ms(mut self, ms: u64) -> Result<Self, ConfigError> {
if ms == 0 {
return Err(ConfigError::InvalidValue(
"flush_interval_ms must be greater than 0".into(),
));
}
self.config.flush_interval_ms = ms;
Ok(self)
}
pub fn wal_dir(mut self, path: std::path::PathBuf) -> Self {
self.config.wal_dir = path;
self
}
pub fn segments_to_retain(mut self, segments: usize) -> Result<Self, ConfigError> {
if segments == 0 {
return Err(ConfigError::InvalidValue(
"segments_to_retain must be greater than 0".into(),
));
}
self.config.segments_to_retain = segments;
Ok(self)
}
pub fn durability_mode(mut self, mode: crate::storage::wal::DurabilityMode) -> Self {
self.config.durability_mode = mode;
self
}
pub fn build(self) -> WalConfig {
self.config
}
}
impl Default for WalConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "config-toml", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "config-toml", serde(default))]
#[non_exhaustive]
pub struct HistoricalConfig {
pub max_versions_per_entity: usize,
pub max_reconstruction_depth: usize,
pub reconstruction_cache_size: usize,
pub anchor_interval: u32,
pub max_delta_chain: u32,
pub enable_cold_storage: bool,
pub cold_storage_path: Option<std::path::PathBuf>,
pub migration_age_threshold: std::time::Duration,
pub max_hot_versions: usize,
}
impl Default for HistoricalConfig {
fn default() -> Self {
let anchor_defaults = AnchorConfig::default();
Self {
max_versions_per_entity: 1000,
max_reconstruction_depth: 100,
reconstruction_cache_size: 10000,
anchor_interval: anchor_defaults.anchor_interval,
max_delta_chain: anchor_defaults.max_delta_chain,
enable_cold_storage: false,
cold_storage_path: None,
migration_age_threshold: std::time::Duration::from_secs(3600), max_hot_versions: 1000,
}
}
}
#[must_use = "builders do nothing unless you call build()"]
#[derive(Debug)]
pub struct HistoricalConfigBuilder {
config: HistoricalConfig,
}
impl HistoricalConfigBuilder {
pub fn new() -> Self {
Self {
config: HistoricalConfig::default(),
}
}
pub fn max_versions_per_entity(mut self, max: usize) -> Result<Self, ConfigError> {
if max == 0 {
return Err(ConfigError::InvalidValue(
"max_versions_per_entity must be greater than 0".into(),
));
}
self.config.max_versions_per_entity = max;
Ok(self)
}
pub fn max_reconstruction_depth(mut self, depth: usize) -> Result<Self, ConfigError> {
if depth == 0 {
return Err(ConfigError::InvalidValue(
"max_reconstruction_depth must be greater than 0".into(),
));
}
if depth > 1000 {
return Err(ConfigError::InvalidValue(
"max_reconstruction_depth cannot exceed 1000 (risk of stack overflow)".into(),
));
}
self.config.max_reconstruction_depth = depth;
Ok(self)
}
pub fn reconstruction_cache_size(mut self, size: usize) -> Result<Self, ConfigError> {
if size == 0 {
return Err(ConfigError::InvalidValue(
"reconstruction_cache_size must be greater than 0".into(),
));
}
self.config.reconstruction_cache_size = size;
Ok(self)
}
pub fn anchor_interval(mut self, interval: u32) -> Result<Self, ConfigError> {
if interval == 0 {
return Err(ConfigError::InvalidValue(
"anchor_interval must be greater than 0".into(),
));
}
self.config.anchor_interval = interval;
Ok(self)
}
pub fn max_delta_chain(mut self, max: u32) -> Result<Self, ConfigError> {
if max == 0 {
return Err(ConfigError::InvalidValue(
"max_delta_chain must be greater than 0".into(),
));
}
self.config.max_delta_chain = max;
Ok(self)
}
pub fn enable_cold_storage(mut self, enabled: bool) -> Self {
self.config.enable_cold_storage = enabled;
self
}
pub fn cold_storage_path<P: Into<std::path::PathBuf>>(mut self, path: P) -> Self {
self.config.cold_storage_path = Some(path.into());
self
}
pub fn migration_age_threshold(mut self, threshold: std::time::Duration) -> Self {
self.config.migration_age_threshold = threshold;
self
}
pub fn max_hot_versions(mut self, max: usize) -> Self {
self.config.max_hot_versions = max;
self
}
pub fn build(self) -> HistoricalConfig {
if self.config.enable_cold_storage && self.config.cold_storage_path.is_none() {
panic!("cold_storage_path must be set when enable_cold_storage is true");
}
self.config
}
pub fn build_checked(self) -> Result<HistoricalConfig, ConfigError> {
if self.config.enable_cold_storage && self.config.cold_storage_path.is_none() {
return Err(ConfigError::InvalidValue(
"cold_storage_path must be set when enable_cold_storage is true".into(),
));
}
Ok(self.config)
}
}
impl Default for HistoricalConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "config-toml", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "config-toml", serde(default))]
#[non_exhaustive]
pub struct VectorIndexConfig {
pub max_k: usize,
pub max_layer: usize,
}
impl Default for VectorIndexConfig {
fn default() -> Self {
Self {
max_k: 10000,
max_layer: 16,
}
}
}
#[must_use = "builders do nothing unless you call build()"]
#[derive(Debug)]
pub struct VectorIndexConfigBuilder {
config: VectorIndexConfig,
}
impl VectorIndexConfigBuilder {
pub fn new() -> Self {
Self {
config: VectorIndexConfig::default(),
}
}
pub fn max_k(mut self, k: usize) -> Result<Self, ConfigError> {
if k == 0 {
return Err(ConfigError::InvalidValue(
"max_k must be greater than 0".into(),
));
}
if k > 100_000 {
return Err(ConfigError::InvalidValue(
"max_k cannot exceed 100,000 (DoS protection)".into(),
));
}
self.config.max_k = k;
Ok(self)
}
pub fn max_layer(mut self, layer: usize) -> Result<Self, ConfigError> {
if layer == 0 {
return Err(ConfigError::InvalidValue(
"max_layer must be greater than 0".into(),
));
}
if layer > 32 {
return Err(ConfigError::InvalidValue(
"max_layer cannot exceed 32 (HNSW limitation)".into(),
));
}
self.config.max_layer = layer;
Ok(self)
}
pub fn build(self) -> VectorIndexConfig {
self.config
}
}
impl Default for VectorIndexConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "config-toml", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "config-toml", serde(default))]
#[non_exhaustive]
pub struct AletheiaDBConfig {
pub wal: WalConfig,
pub historical: HistoricalConfig,
pub vector: VectorIndexConfig,
pub persistence: PersistenceConfig,
pub encryption: crate::encryption::config::EncryptionConfig,
}
#[must_use = "builders do nothing unless you call build()"]
#[derive(Debug)]
pub struct AletheiaDBConfigBuilder {
config: AletheiaDBConfig,
}
impl AletheiaDBConfigBuilder {
pub fn new() -> Self {
Self {
config: AletheiaDBConfig::default(),
}
}
pub fn wal(mut self, wal_config: WalConfig) -> Self {
self.config.wal = wal_config;
self
}
pub fn historical(mut self, historical_config: HistoricalConfig) -> Self {
self.config.historical = historical_config;
self
}
pub fn vector(mut self, vector_config: VectorIndexConfig) -> Self {
self.config.vector = vector_config;
self
}
pub fn persistence(mut self, persistence_config: PersistenceConfig) -> Self {
self.config.persistence = persistence_config;
self
}
pub fn encryption(
mut self,
encryption_config: crate::encryption::config::EncryptionConfig,
) -> Self {
self.config.encryption = encryption_config;
self
}
pub fn build(self) -> AletheiaDBConfig {
self.config
}
}
impl Default for AletheiaDBConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl AletheiaDBConfig {
pub fn builder() -> AletheiaDBConfigBuilder {
AletheiaDBConfigBuilder::new()
}
#[cfg(feature = "config-toml")]
pub fn from_toml_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
let contents =
fs::read_to_string(path.as_ref()).map_err(|e| ConfigError::IoError(e.to_string()))?;
Self::from_toml_str(&contents)
}
#[cfg(feature = "config-toml")]
pub fn from_toml_str(s: &str) -> Result<Self, ConfigError> {
toml::from_str(s).map_err(|e| ConfigError::ParseError(e.to_string()))
}
#[cfg(feature = "config-toml")]
pub fn to_toml_file<P: AsRef<Path>>(&self, path: P) -> Result<(), ConfigError> {
let toml_string = self.to_toml_string()?;
fs::write(path.as_ref(), toml_string).map_err(|e| ConfigError::IoError(e.to_string()))?;
Ok(())
}
#[cfg(feature = "config-toml")]
pub fn to_toml_string(&self) -> Result<String, ConfigError> {
toml::to_string_pretty(self).map_err(|e| ConfigError::SerializeError(e.to_string()))
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ConfigError {
#[error("I/O error: {0}")]
IoError(String),
#[error("Parse error: {0}")]
ParseError(String),
#[error("Serialize error: {0}")]
SerializeError(String),
#[error("Invalid value: {0}")]
InvalidValue(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wal_config_defaults() {
let config = WalConfig::default();
assert_eq!(config.num_stripes, 16);
assert_eq!(config.stripe_capacity, 1024);
assert_eq!(config.write_buffer_size, 64 * 1024);
assert_eq!(config.segment_size, 64 * 1024 * 1024);
assert_eq!(config.flush_interval_ms, 10);
}
#[test]
fn test_wal_config_builder() {
let config = WalConfigBuilder::new()
.num_stripes(32)
.unwrap()
.stripe_capacity(2048)
.unwrap()
.write_buffer_size(128 * 1024)
.unwrap()
.segment_size(128 * 1024 * 1024)
.unwrap()
.flush_interval_ms(20)
.unwrap()
.build();
assert_eq!(config.num_stripes, 32);
assert_eq!(config.stripe_capacity, 2048);
assert_eq!(config.write_buffer_size, 128 * 1024);
assert_eq!(config.segment_size, 128 * 1024 * 1024);
assert_eq!(config.flush_interval_ms, 20);
}
#[test]
fn test_wal_config_builder_rounds_stripes_to_power_of_two() {
let config = WalConfigBuilder::new()
.num_stripes(30)
.unwrap() .build();
assert_eq!(config.num_stripes, 32); }
#[test]
fn test_wal_config_with_validated() {
let config = WalConfigBuilder::new()
.with_validated(
32, 2048, 128 * 1024, 64 * 1024 * 1024, 10, 20, )
.unwrap() .build();
assert_eq!(config.num_stripes, 32);
assert_eq!(config.stripe_capacity, 2048);
assert_eq!(config.write_buffer_size, 128 * 1024);
assert_eq!(config.segment_size, 64 * 1024 * 1024);
assert_eq!(config.segments_to_retain, 10);
assert_eq!(config.flush_interval_ms, 20);
}
#[test]
fn test_wal_config_with_validated_invalid() {
let result = WalConfigBuilder::new().with_validated(
0, 2048,
128 * 1024,
64 * 1024 * 1024,
10,
20,
);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_historical_config_defaults() {
let config = HistoricalConfig::default();
assert_eq!(config.max_versions_per_entity, 1000);
assert_eq!(config.max_reconstruction_depth, 100);
assert_eq!(config.reconstruction_cache_size, 10000);
assert_eq!(config.anchor_interval, 10);
assert_eq!(config.max_delta_chain, 20);
}
#[test]
fn test_historical_config_builder() {
let config = HistoricalConfigBuilder::new()
.max_versions_per_entity(5000)
.unwrap()
.max_reconstruction_depth(200)
.unwrap()
.reconstruction_cache_size(20000)
.unwrap()
.anchor_interval(5)
.unwrap()
.max_delta_chain(10)
.unwrap()
.build();
assert_eq!(config.max_versions_per_entity, 5000);
assert_eq!(config.max_reconstruction_depth, 200);
assert_eq!(config.reconstruction_cache_size, 20000);
assert_eq!(config.anchor_interval, 5);
assert_eq!(config.max_delta_chain, 10);
}
#[test]
fn test_vector_config_defaults() {
let config = VectorIndexConfig::default();
assert_eq!(config.max_k, 10000);
assert_eq!(config.max_layer, 16);
}
#[test]
fn test_vector_config_builder() {
let config = VectorIndexConfigBuilder::new()
.max_k(20000)
.unwrap()
.max_layer(32)
.unwrap()
.build();
assert_eq!(config.max_k, 20000);
assert_eq!(config.max_layer, 32);
}
#[test]
fn test_unified_config_defaults() {
let config = AletheiaDBConfig::default();
assert_eq!(config.wal, WalConfig::default());
assert_eq!(config.historical, HistoricalConfig::default());
assert_eq!(config.vector, VectorIndexConfig::default());
}
#[test]
fn test_unified_config_builder() {
let config = AletheiaDBConfig::builder()
.wal(WalConfigBuilder::new().num_stripes(32).unwrap().build())
.historical(
HistoricalConfigBuilder::new()
.max_versions_per_entity(5000)
.unwrap()
.build(),
)
.vector(
VectorIndexConfigBuilder::new()
.max_k(20000)
.unwrap()
.build(),
)
.build();
assert_eq!(config.wal.num_stripes, 32);
assert_eq!(config.historical.max_versions_per_entity, 5000);
assert_eq!(config.vector.max_k, 20000);
}
#[test]
fn test_wal_config_fluent_api() {
let config = WalConfigBuilder::new()
.num_stripes(8)
.unwrap()
.stripe_capacity(512)
.unwrap()
.build();
assert_eq!(config.num_stripes, 8);
assert_eq!(config.stripe_capacity, 512);
}
#[test]
fn test_embedded_system_config() {
let config = AletheiaDBConfig::builder()
.wal(
WalConfigBuilder::new()
.num_stripes(4)
.unwrap()
.stripe_capacity(256)
.unwrap()
.write_buffer_size(16 * 1024)
.unwrap()
.segment_size(16 * 1024 * 1024)
.unwrap()
.build(),
)
.historical(
HistoricalConfigBuilder::new()
.max_versions_per_entity(100)
.unwrap()
.reconstruction_cache_size(1000)
.unwrap()
.build(),
)
.build();
assert_eq!(config.wal.num_stripes, 4);
assert_eq!(config.wal.write_buffer_size, 16 * 1024);
assert_eq!(config.historical.max_versions_per_entity, 100);
}
#[test]
fn test_cloud_deployment_config() {
let config = AletheiaDBConfig::builder()
.wal(
WalConfigBuilder::new()
.num_stripes(64)
.unwrap()
.stripe_capacity(4096)
.unwrap()
.write_buffer_size(256 * 1024)
.unwrap()
.segment_size(256 * 1024 * 1024)
.unwrap()
.build(),
)
.historical(
HistoricalConfigBuilder::new()
.max_versions_per_entity(10000)
.unwrap()
.reconstruction_cache_size(100000)
.unwrap()
.build(),
)
.build();
assert_eq!(config.wal.num_stripes, 64);
assert_eq!(config.wal.write_buffer_size, 256 * 1024);
assert_eq!(config.historical.max_versions_per_entity, 10000);
}
#[test]
fn test_batch_processing_config() {
let config = AletheiaDBConfig::builder()
.wal(
WalConfigBuilder::new()
.flush_interval_ms(100)
.unwrap() .build(),
)
.build();
assert_eq!(config.wal.flush_interval_ms, 100);
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_serialization() {
let config = AletheiaDBConfig::default();
let toml_string = config.to_toml_string().unwrap();
assert!(toml_string.contains("[wal]"));
assert!(toml_string.contains("[historical]"));
assert!(toml_string.contains("[vector]"));
assert!(toml_string.contains("num_stripes"));
assert!(toml_string.contains("max_versions_per_entity"));
assert!(toml_string.contains("max_k"));
assert!(toml_string.contains("anchor_interval"));
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_deserialization_partial() {
let toml_str = r#"
[wal]
num_stripes = 32
stripe_capacity = 2048
[historical]
max_versions_per_entity = 1000
max_reconstruction_depth = 100
reconstruction_cache_size = 10000
[vector]
max_k = 10000
max_layer = 16
"#;
let config = AletheiaDBConfig::from_toml_str(toml_str).unwrap();
assert_eq!(config.wal.num_stripes, 32);
assert_eq!(config.wal.stripe_capacity, 2048);
assert_eq!(config.wal.write_buffer_size, 64 * 1024);
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_deserialization_complete() {
let toml_str = r#"
[wal]
num_stripes = 32
stripe_capacity = 2048
write_buffer_size = 131072
segment_size = 134217728
flush_interval_ms = 20
[historical]
max_versions_per_entity = 5000
max_reconstruction_depth = 200
reconstruction_cache_size = 20000
anchor_interval = 5
max_delta_chain = 10
[vector]
max_k = 20000
max_layer = 32
"#;
let config = AletheiaDBConfig::from_toml_str(toml_str).unwrap();
assert_eq!(config.wal.num_stripes, 32);
assert_eq!(config.wal.stripe_capacity, 2048);
assert_eq!(config.wal.write_buffer_size, 131072);
assert_eq!(config.wal.segment_size, 134217728);
assert_eq!(config.wal.flush_interval_ms, 20);
assert_eq!(config.historical.max_versions_per_entity, 5000);
assert_eq!(config.historical.max_reconstruction_depth, 200);
assert_eq!(config.historical.reconstruction_cache_size, 20000);
assert_eq!(config.historical.anchor_interval, 5);
assert_eq!(config.historical.max_delta_chain, 10);
assert_eq!(config.vector.max_k, 20000);
assert_eq!(config.vector.max_layer, 32);
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_round_trip() {
let original = AletheiaDBConfig::builder()
.wal(
WalConfigBuilder::new()
.num_stripes(32)
.unwrap()
.stripe_capacity(2048)
.unwrap()
.build(),
)
.historical(
HistoricalConfigBuilder::new()
.max_versions_per_entity(5000)
.unwrap()
.build(),
)
.vector(
VectorIndexConfigBuilder::new()
.max_k(20000)
.unwrap()
.build(),
)
.build();
let toml_string = original.to_toml_string().unwrap();
let deserialized = AletheiaDBConfig::from_toml_str(&toml_string).unwrap();
assert_eq!(original, deserialized);
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_file_save_and_load() {
use tempfile::NamedTempFile;
let config = AletheiaDBConfig::builder()
.wal(WalConfigBuilder::new().num_stripes(64).unwrap().build())
.build();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path();
config.to_toml_file(path).unwrap();
let loaded = AletheiaDBConfig::from_toml_file(path).unwrap();
assert_eq!(config, loaded);
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_embedded_system_example() {
let toml_str = r#"
# Embedded system configuration
[wal]
num_stripes = 4
stripe_capacity = 256
write_buffer_size = 16384
segment_size = 16777216
[historical]
max_versions_per_entity = 100
max_reconstruction_depth = 50
reconstruction_cache_size = 1000
[vector]
max_k = 1000
max_layer = 8
"#;
let config = AletheiaDBConfig::from_toml_str(toml_str).unwrap();
assert_eq!(config.wal.num_stripes, 4);
assert_eq!(config.wal.stripe_capacity, 256);
assert_eq!(config.historical.max_versions_per_entity, 100);
assert_eq!(config.vector.max_k, 1000);
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_cloud_deployment_example() {
let toml_str = r#"
# Cloud deployment configuration
[wal]
num_stripes = 64
stripe_capacity = 4096
write_buffer_size = 262144
segment_size = 268435456
[historical]
max_versions_per_entity = 10000
max_reconstruction_depth = 200
reconstruction_cache_size = 100000
[vector]
max_k = 50000
max_layer = 24
"#;
let config = AletheiaDBConfig::from_toml_str(toml_str).unwrap();
assert_eq!(config.wal.num_stripes, 64);
assert_eq!(config.wal.stripe_capacity, 4096);
assert_eq!(config.historical.max_versions_per_entity, 10000);
assert_eq!(config.vector.max_k, 50000);
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_parse_error() {
let invalid_toml = "this is not valid toml {]";
let result = AletheiaDBConfig::from_toml_str(invalid_toml);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::ParseError(_)));
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_durability_mode_group_commit() {
let toml_str = r#"
[wal]
num_stripes = 32
[wal.durability_mode.GroupCommit]
max_delay_ms = 10
max_batch_size = 200
"#;
let config = AletheiaDBConfig::from_toml_str(toml_str).unwrap();
assert_eq!(config.wal.num_stripes, 32);
match config.wal.durability_mode {
crate::storage::wal::DurabilityMode::GroupCommit {
max_delay_ms,
max_batch_size,
} => {
assert_eq!(max_delay_ms, 10);
assert_eq!(max_batch_size, 200);
}
_ => panic!("Expected GroupCommit durability mode"),
}
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_durability_mode_async() {
let toml_str = r#"
[wal]
[wal.durability_mode.Async]
flush_interval_ms = 100
"#;
let config = AletheiaDBConfig::from_toml_str(toml_str).unwrap();
match config.wal.durability_mode {
crate::storage::wal::DurabilityMode::Async { flush_interval_ms } => {
assert_eq!(flush_interval_ms, 100);
}
_ => panic!("Expected Async durability mode"),
}
}
#[test]
#[cfg(feature = "config-toml")]
fn test_toml_wal_dir() {
use std::path::PathBuf;
let toml_str = r#"
[wal]
wal_dir = "/custom/path/to/wal"
"#;
let config = AletheiaDBConfig::from_toml_str(toml_str).unwrap();
assert_eq!(config.wal.wal_dir, PathBuf::from("/custom/path/to/wal"));
}
#[test]
fn test_wal_config_zero_num_stripes() {
let result = WalConfigBuilder::new().num_stripes(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_wal_config_zero_stripe_capacity() {
let result = WalConfigBuilder::new().stripe_capacity(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_wal_config_zero_write_buffer_size() {
let result = WalConfigBuilder::new().write_buffer_size(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_wal_config_zero_segment_size() {
let result = WalConfigBuilder::new().segment_size(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_wal_config_segment_size_too_small() {
let result = WalConfigBuilder::new().segment_size(256); assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_wal_config_zero_segments_to_retain() {
let result = WalConfigBuilder::new().segments_to_retain(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_wal_config_zero_flush_interval() {
let result = WalConfigBuilder::new().flush_interval_ms(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_historical_config_zero_max_versions() {
let result = HistoricalConfigBuilder::new().max_versions_per_entity(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_historical_config_zero_max_reconstruction_depth() {
let result = HistoricalConfigBuilder::new().max_reconstruction_depth(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_historical_config_max_reconstruction_depth_too_large() {
let result = HistoricalConfigBuilder::new().max_reconstruction_depth(2000);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_historical_config_zero_cache_size() {
let result = HistoricalConfigBuilder::new().reconstruction_cache_size(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_historical_config_zero_anchor_interval() {
let result = HistoricalConfigBuilder::new().anchor_interval(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_historical_config_zero_max_delta_chain() {
let result = HistoricalConfigBuilder::new().max_delta_chain(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_historical_config_build_checked_cold_storage_missing_path() {
let result = HistoricalConfigBuilder::new()
.enable_cold_storage(true)
.build_checked();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_historical_config_build_checked_cold_storage_valid_path() {
use std::path::PathBuf;
let result = HistoricalConfigBuilder::new()
.enable_cold_storage(true)
.cold_storage_path(PathBuf::from("/tmp/test"))
.build_checked();
assert!(result.is_ok());
}
#[test]
fn test_vector_config_zero_max_k() {
let result = VectorIndexConfigBuilder::new().max_k(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_vector_config_max_k_too_large() {
let result = VectorIndexConfigBuilder::new().max_k(200_000);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_vector_config_zero_max_layer() {
let result = VectorIndexConfigBuilder::new().max_layer(0);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
#[test]
fn test_vector_config_max_layer_too_large() {
let result = VectorIndexConfigBuilder::new().max_layer(64);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidValue(_)));
}
}