use crate::error::{Error, Result};
use crate::storage_ceilings::StorageCeilings;
use serde::{Deserialize, Serialize};
use std::sync::{OnceLock, RwLock};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum Durability {
#[default]
Always,
Interval,
Manual,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum IoBackend {
#[default]
Positioned,
Mmap,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigBuilder {
pub(crate) durability: Durability,
pub(crate) wal_max_ops: Option<u64>,
pub(crate) wal_max_bytes: Option<u64>,
#[serde(default)]
pub(crate) io_backend: IoBackend,
#[serde(default)]
pub(crate) storage_ceilings: StorageCeilings,
}
impl ConfigBuilder {
pub fn new() -> Self {
Self {
durability: Durability::Always,
wal_max_ops: None,
wal_max_bytes: None,
io_backend: IoBackend::Positioned,
storage_ceilings: StorageCeilings::default(),
}
}
pub fn durability(mut self, durability: Durability) -> Self {
self.durability = durability;
self
}
pub fn wal_max_ops(mut self, limit: u64) -> Self {
self.wal_max_ops = (limit > 0).then_some(limit);
self
}
pub fn wal_max_bytes(mut self, limit: u64) -> Self {
self.wal_max_bytes = (limit > 0).then_some(limit);
self
}
pub fn io_backend(mut self, backend: IoBackend) -> Self {
self.io_backend = backend;
self
}
pub fn storage_ceilings(mut self, ceilings: StorageCeilings) -> Self {
self.storage_ceilings = ceilings;
self
}
pub fn build(self) -> Self {
self
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self::new()
}
}
static CONFIG: OnceLock<RwLock<ConfigBuilder>> = OnceLock::new();
fn config_cell() -> &'static RwLock<ConfigBuilder> {
CONFIG.get_or_init(|| RwLock::new(ConfigBuilder::default()))
}
pub fn default_config() -> ConfigBuilder {
ConfigBuilder::default()
}
pub fn initialize(config: Option<&ConfigBuilder>) -> Result<()> {
let chosen = config.cloned().unwrap_or_default();
*config_cell()
.write()
.map_err(|_| Error::internal("configuration lock poisoned"))? = chosen;
INITIALIZED.store(true, std::sync::atomic::Ordering::Release);
Ok(())
}
pub fn is_initialized() -> bool {
INITIALIZED.load(std::sync::atomic::Ordering::Acquire)
}
static INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub(crate) fn current_config() -> ConfigBuilder {
config_cell()
.read()
.map_or_else(|_| ConfigBuilder::default(), |v| v.clone())
}
pub fn shutdown() -> Result<()> {
*config_cell()
.write()
.map_err(|_| Error::internal("configuration lock poisoned"))? = ConfigBuilder::default();
INITIALIZED.store(false, std::sync::atomic::Ordering::Release);
Ok(())
}
pub fn version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
pub fn check_version(major: i32, minor: i32, patch: i32) -> bool {
let mut pieces = env!("CARGO_PKG_VERSION").split('.');
let current = (
pieces
.next()
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(0),
pieces
.next()
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(0),
pieces
.next()
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(0),
);
current >= (major, minor, patch)
}
pub fn version_major() -> i32 {
env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap_or(0)
}
pub fn version_minor() -> i32 {
env!("CARGO_PKG_VERSION_MINOR").parse().unwrap_or(0)
}
pub fn version_patch() -> i32 {
env!("CARGO_PKG_VERSION_PATCH").parse().unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_portable() {
let cfg = ConfigBuilder::default();
assert_eq!(cfg.durability, Durability::Always);
assert_eq!(cfg.io_backend, IoBackend::Positioned);
assert_eq!(cfg.storage_ceilings, StorageCeilings::default());
}
#[test]
fn zero_checkpoint_limits_are_disabled() {
let cfg = ConfigBuilder::default().wal_max_ops(0).wal_max_bytes(0);
assert_eq!(cfg.wal_max_ops, None);
assert_eq!(cfg.wal_max_bytes, None);
}
}