1use clap::Parser;
4use ormdb_core::storage::RetentionPolicy;
5use std::path::PathBuf;
6use std::time::Duration;
7
8pub const DEFAULT_TCP_ADDRESS: &str = "tcp://0.0.0.0:9000";
10
11pub const DEFAULT_REQUEST_TIMEOUT_SECS: u64 = 30;
13
14pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024;
16
17pub 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#[derive(Debug, Clone)]
29pub struct ServerConfig {
30 pub tcp_address: Option<String>,
32
33 pub ipc_address: Option<String>,
35
36 pub data_path: PathBuf,
38
39 pub request_timeout: Duration,
41
42 pub max_message_size: usize,
44
45 pub retention_policy: RetentionPolicy,
47
48 pub compaction_interval: Option<Duration>,
50
51 pub transport_workers: usize,
53}
54
55impl ServerConfig {
56 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 pub fn with_tcp_address(mut self, address: impl Into<String>) -> Self {
72 self.tcp_address = Some(address.into());
73 self
74 }
75
76 pub fn without_tcp(mut self) -> Self {
78 self.tcp_address = None;
79 self
80 }
81
82 pub fn with_ipc_address(mut self, address: impl Into<String>) -> Self {
84 self.ipc_address = Some(address.into());
85 self
86 }
87
88 pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
90 self.request_timeout = timeout;
91 self
92 }
93
94 pub fn with_max_message_size(mut self, size: usize) -> Self {
96 self.max_message_size = size;
97 self
98 }
99
100 pub fn with_retention_policy(mut self, policy: RetentionPolicy) -> Self {
102 self.retention_policy = policy;
103 self
104 }
105
106 pub fn with_compaction_interval(mut self, interval: Duration) -> Self {
108 self.compaction_interval = Some(interval);
109 self
110 }
111
112 pub fn without_compaction(mut self) -> Self {
114 self.compaction_interval = None;
115 self
116 }
117
118 pub fn with_transport_workers(mut self, workers: usize) -> Self {
120 self.transport_workers = workers.max(1);
121 self
122 }
123
124 pub fn has_transport(&self) -> bool {
126 self.tcp_address.is_some() || self.ipc_address.is_some()
127 }
128
129 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#[derive(Parser, Debug)]
143#[command(name = "ormdb-server")]
144#[command(version, about = "ORMDB Database Server", long_about = None)]
145pub struct Args {
146 #[arg(short, long, default_value = "./data")]
148 pub data_path: PathBuf,
149
150 #[arg(long, default_value = DEFAULT_TCP_ADDRESS)]
152 pub tcp: String,
153
154 #[arg(long)]
156 pub ipc: Option<String>,
157
158 #[arg(long, default_value_t = DEFAULT_REQUEST_TIMEOUT_SECS)]
160 pub timeout: u64,
161
162 #[arg(long, default_value_t = 64)]
164 pub max_message_mb: usize,
165
166 #[arg(long)]
168 pub no_tcp: bool,
169
170 #[arg(long, default_value_t = DEFAULT_COMPACTION_INTERVAL_SECS)]
172 pub compaction_interval: u64,
173
174 #[arg(long, default_value_t = 100)]
176 pub max_versions: usize,
177
178 #[arg(long, default_value_t = 0)]
180 pub workers: usize,
181}
182
183impl Args {
184 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}