Skip to main content

ormdb_server/
config.rs

1//! Server configuration.
2
3use clap::Parser;
4use ormdb_core::storage::RetentionPolicy;
5use std::path::PathBuf;
6use std::time::Duration;
7
8/// Default TCP address for the server.
9pub const DEFAULT_TCP_ADDRESS: &str = "tcp://0.0.0.0:9000";
10
11/// Default request timeout in seconds.
12pub const DEFAULT_REQUEST_TIMEOUT_SECS: u64 = 30;
13
14/// Default maximum message size (64 MB).
15pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024;
16
17/// Default compaction interval in seconds (1 hour).
18pub const DEFAULT_COMPACTION_INTERVAL_SECS: u64 = 3600;
19
20fn default_transport_workers() -> usize {
21    std::thread::available_parallelism()
22        .map(|count| count.get())
23        .unwrap_or(4)
24        .max(1)
25}
26
27/// ORMDB Server configuration.
28#[derive(Debug, Clone)]
29pub struct ServerConfig {
30    /// TCP address to bind to (e.g., "tcp://0.0.0.0:9000").
31    pub tcp_address: Option<String>,
32
33    /// IPC address to bind to (e.g., "ipc:///tmp/ormdb.sock").
34    pub ipc_address: Option<String>,
35
36    /// Path to the database storage directory.
37    pub data_path: PathBuf,
38
39    /// Request timeout duration.
40    pub request_timeout: Duration,
41
42    /// Maximum message size in bytes.
43    pub max_message_size: usize,
44
45    /// Version retention policy for compaction.
46    pub retention_policy: RetentionPolicy,
47
48    /// Interval between automatic compaction runs. None disables auto-compaction.
49    pub compaction_interval: Option<Duration>,
50
51    /// Number of transport worker loops (AsyncContext instances).
52    pub transport_workers: usize,
53}
54
55impl ServerConfig {
56    /// Create a new server configuration with the given data path.
57    pub fn new(data_path: impl Into<PathBuf>) -> Self {
58        Self {
59            tcp_address: Some(DEFAULT_TCP_ADDRESS.to_string()),
60            ipc_address: None,
61            data_path: data_path.into(),
62            request_timeout: Duration::from_secs(DEFAULT_REQUEST_TIMEOUT_SECS),
63            max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
64            retention_policy: RetentionPolicy::default(),
65            compaction_interval: Some(Duration::from_secs(DEFAULT_COMPACTION_INTERVAL_SECS)),
66            transport_workers: default_transport_workers(),
67        }
68    }
69
70    /// Set the TCP address.
71    pub fn with_tcp_address(mut self, address: impl Into<String>) -> Self {
72        self.tcp_address = Some(address.into());
73        self
74    }
75
76    /// Disable TCP transport.
77    pub fn without_tcp(mut self) -> Self {
78        self.tcp_address = None;
79        self
80    }
81
82    /// Set the IPC address.
83    pub fn with_ipc_address(mut self, address: impl Into<String>) -> Self {
84        self.ipc_address = Some(address.into());
85        self
86    }
87
88    /// Set the request timeout.
89    pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
90        self.request_timeout = timeout;
91        self
92    }
93
94    /// Set the maximum message size.
95    pub fn with_max_message_size(mut self, size: usize) -> Self {
96        self.max_message_size = size;
97        self
98    }
99
100    /// Set the retention policy for version compaction.
101    pub fn with_retention_policy(mut self, policy: RetentionPolicy) -> Self {
102        self.retention_policy = policy;
103        self
104    }
105
106    /// Set the compaction interval.
107    pub fn with_compaction_interval(mut self, interval: Duration) -> Self {
108        self.compaction_interval = Some(interval);
109        self
110    }
111
112    /// Disable automatic compaction.
113    pub fn without_compaction(mut self) -> Self {
114        self.compaction_interval = None;
115        self
116    }
117
118    /// Set the number of transport worker loops.
119    pub fn with_transport_workers(mut self, workers: usize) -> Self {
120        self.transport_workers = workers.max(1);
121        self
122    }
123
124    /// Check if at least one transport is configured.
125    pub fn has_transport(&self) -> bool {
126        self.tcp_address.is_some() || self.ipc_address.is_some()
127    }
128
129    /// Check if automatic compaction is enabled.
130    pub fn has_compaction(&self) -> bool {
131        self.compaction_interval.is_some()
132    }
133}
134
135impl Default for ServerConfig {
136    fn default() -> Self {
137        Self::new("./data")
138    }
139}
140
141/// Command-line arguments for the server.
142#[derive(Parser, Debug)]
143#[command(name = "ormdb-server")]
144#[command(version, about = "ORMDB Database Server", long_about = None)]
145pub struct Args {
146    /// Path to the database storage directory.
147    #[arg(short, long, default_value = "./data")]
148    pub data_path: PathBuf,
149
150    /// TCP address to bind to.
151    #[arg(long, default_value = DEFAULT_TCP_ADDRESS)]
152    pub tcp: String,
153
154    /// IPC address to bind to (optional).
155    #[arg(long)]
156    pub ipc: Option<String>,
157
158    /// Request timeout in seconds.
159    #[arg(long, default_value_t = DEFAULT_REQUEST_TIMEOUT_SECS)]
160    pub timeout: u64,
161
162    /// Maximum message size in megabytes.
163    #[arg(long, default_value_t = 64)]
164    pub max_message_mb: usize,
165
166    /// Disable TCP transport (requires --ipc to be set).
167    #[arg(long)]
168    pub no_tcp: bool,
169
170    /// Compaction interval in seconds. Set to 0 to disable auto-compaction.
171    #[arg(long, default_value_t = DEFAULT_COMPACTION_INTERVAL_SECS)]
172    pub compaction_interval: u64,
173
174    /// Maximum versions to keep per entity.
175    #[arg(long, default_value_t = 100)]
176    pub max_versions: usize,
177
178    /// Transport worker loops (0 = auto).
179    #[arg(long, default_value_t = 0)]
180    pub workers: usize,
181}
182
183impl Args {
184    /// Convert command-line arguments to server configuration.
185    pub fn into_config(self) -> ServerConfig {
186        let tcp_address = if self.no_tcp { None } else { Some(self.tcp) };
187
188        let compaction_interval = if self.compaction_interval == 0 {
189            None
190        } else {
191            Some(Duration::from_secs(self.compaction_interval))
192        };
193
194        let retention_policy = RetentionPolicy::with_max_versions(self.max_versions);
195        let transport_workers = if self.workers == 0 {
196            default_transport_workers()
197        } else {
198            self.workers.max(1)
199        };
200
201        ServerConfig {
202            tcp_address,
203            ipc_address: self.ipc,
204            data_path: self.data_path,
205            request_timeout: Duration::from_secs(self.timeout),
206            max_message_size: self.max_message_mb * 1024 * 1024,
207            retention_policy,
208            compaction_interval,
209            transport_workers,
210        }
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_default_config() {
220        let config = ServerConfig::default();
221        assert_eq!(config.tcp_address, Some(DEFAULT_TCP_ADDRESS.to_string()));
222        assert!(config.ipc_address.is_none());
223        assert_eq!(config.data_path, PathBuf::from("./data"));
224        assert_eq!(
225            config.request_timeout,
226            Duration::from_secs(DEFAULT_REQUEST_TIMEOUT_SECS)
227        );
228        assert!(config.has_transport());
229    }
230
231    #[test]
232    fn test_config_builder() {
233        let config = ServerConfig::new("/var/lib/ormdb")
234            .with_tcp_address("tcp://127.0.0.1:8080")
235            .with_ipc_address("ipc:///tmp/ormdb.sock")
236            .with_request_timeout(Duration::from_secs(60))
237            .with_max_message_size(128 * 1024 * 1024);
238
239        assert_eq!(
240            config.tcp_address,
241            Some("tcp://127.0.0.1:8080".to_string())
242        );
243        assert_eq!(
244            config.ipc_address,
245            Some("ipc:///tmp/ormdb.sock".to_string())
246        );
247        assert_eq!(config.data_path, PathBuf::from("/var/lib/ormdb"));
248        assert_eq!(config.request_timeout, Duration::from_secs(60));
249        assert_eq!(config.max_message_size, 128 * 1024 * 1024);
250    }
251
252    #[test]
253    fn test_without_tcp() {
254        let config = ServerConfig::new("./data")
255            .without_tcp()
256            .with_ipc_address("ipc:///tmp/ormdb.sock");
257
258        assert!(config.tcp_address.is_none());
259        assert!(config.ipc_address.is_some());
260        assert!(config.has_transport());
261    }
262
263    #[test]
264    fn test_no_transport() {
265        let config = ServerConfig::new("./data").without_tcp();
266        assert!(!config.has_transport());
267    }
268}