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