1use crate::error::{Error, Result};
4use crate::storage_ceilings::StorageCeilings;
5use serde::{Deserialize, Serialize};
6use std::sync::{OnceLock, RwLock};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
10pub enum Durability {
11 #[default]
13 Always,
14 Interval,
16 Manual,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
22#[serde(rename_all = "snake_case")]
23pub enum IoBackend {
24 #[default]
27 Positioned,
28 Mmap,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ConfigBuilder {
59 pub(crate) durability: Durability,
60 pub(crate) wal_max_ops: Option<u64>,
61 pub(crate) wal_max_bytes: Option<u64>,
62 #[serde(default)]
63 pub(crate) io_backend: IoBackend,
64 #[serde(default)]
67 pub(crate) storage_ceilings: StorageCeilings,
68}
69
70impl ConfigBuilder {
71 pub fn new() -> Self {
72 Self {
73 durability: Durability::Always,
74 wal_max_ops: None,
75 wal_max_bytes: None,
76 io_backend: IoBackend::Positioned,
77 storage_ceilings: StorageCeilings::default(),
78 }
79 }
80
81 pub fn durability(mut self, durability: Durability) -> Self {
82 self.durability = durability;
83 self
84 }
85
86 pub fn wal_max_ops(mut self, limit: u64) -> Self {
87 self.wal_max_ops = (limit > 0).then_some(limit);
88 self
89 }
90
91 pub fn wal_max_bytes(mut self, limit: u64) -> Self {
92 self.wal_max_bytes = (limit > 0).then_some(limit);
93 self
94 }
95
96 pub fn io_backend(mut self, backend: IoBackend) -> Self {
98 self.io_backend = backend;
99 self
100 }
101
102 pub fn storage_ceilings(mut self, ceilings: StorageCeilings) -> Self {
105 self.storage_ceilings = ceilings;
106 self
107 }
108
109 pub fn build(self) -> Self {
111 self
112 }
113}
114
115impl Default for ConfigBuilder {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121static CONFIG: OnceLock<RwLock<ConfigBuilder>> = OnceLock::new();
122
123fn config_cell() -> &'static RwLock<ConfigBuilder> {
124 CONFIG.get_or_init(|| RwLock::new(ConfigBuilder::default()))
125}
126
127pub fn default_config() -> ConfigBuilder {
129 ConfigBuilder::default()
130}
131
132pub fn initialize(config: Option<&ConfigBuilder>) -> Result<()> {
135 let chosen = config.cloned().unwrap_or_default();
136 *config_cell()
137 .write()
138 .map_err(|_| Error::internal("configuration lock poisoned"))? = chosen;
139 INITIALIZED.store(true, std::sync::atomic::Ordering::Release);
140 Ok(())
141}
142
143pub fn is_initialized() -> bool {
148 INITIALIZED.load(std::sync::atomic::Ordering::Acquire)
149}
150
151static INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
152
153pub(crate) fn current_config() -> ConfigBuilder {
155 config_cell()
156 .read()
157 .map_or_else(|_| ConfigBuilder::default(), |v| v.clone())
158}
159
160pub fn shutdown() -> Result<()> {
162 *config_cell()
163 .write()
164 .map_err(|_| Error::internal("configuration lock poisoned"))? = ConfigBuilder::default();
165 INITIALIZED.store(false, std::sync::atomic::Ordering::Release);
166 Ok(())
167}
168
169pub fn version() -> String {
171 env!("CARGO_PKG_VERSION").to_string()
172}
173
174pub fn check_version(major: i32, minor: i32, patch: i32) -> bool {
175 let mut pieces = env!("CARGO_PKG_VERSION").split('.');
176 let current = (
177 pieces
178 .next()
179 .and_then(|v| v.parse::<i32>().ok())
180 .unwrap_or(0),
181 pieces
182 .next()
183 .and_then(|v| v.parse::<i32>().ok())
184 .unwrap_or(0),
185 pieces
186 .next()
187 .and_then(|v| v.parse::<i32>().ok())
188 .unwrap_or(0),
189 );
190 current >= (major, minor, patch)
191}
192
193pub fn version_major() -> i32 {
194 env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap_or(0)
195}
196
197pub fn version_minor() -> i32 {
198 env!("CARGO_PKG_VERSION_MINOR").parse().unwrap_or(0)
199}
200
201pub fn version_patch() -> i32 {
202 env!("CARGO_PKG_VERSION_PATCH").parse().unwrap_or(0)
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn defaults_are_portable() {
211 let cfg = ConfigBuilder::default();
212 assert_eq!(cfg.durability, Durability::Always);
213 assert_eq!(cfg.io_backend, IoBackend::Positioned);
214 assert_eq!(cfg.storage_ceilings, StorageCeilings::default());
215 }
216
217 #[test]
218 fn zero_checkpoint_limits_are_disabled() {
219 let cfg = ConfigBuilder::default().wal_max_ops(0).wal_max_bytes(0);
220 assert_eq!(cfg.wal_max_ops, None);
221 assert_eq!(cfg.wal_max_bytes, None);
222 }
223}