1use std::path::PathBuf;
4
5#[derive(Debug, Clone)]
7#[allow(clippy::struct_excessive_bools)] pub struct Config {
9 pub path: Option<PathBuf>,
11
12 pub memory_limit: Option<usize>,
14
15 pub spill_path: Option<PathBuf>,
17
18 pub threads: usize,
20
21 pub wal_enabled: bool,
23
24 pub wal_flush_interval_ms: u64,
26
27 pub backward_edges: bool,
29
30 pub query_logging: bool,
32
33 pub adaptive: AdaptiveConfig,
35
36 pub factorized_execution: bool,
44}
45
46#[derive(Debug, Clone)]
51pub struct AdaptiveConfig {
52 pub enabled: bool,
54
55 pub threshold: f64,
60
61 pub min_rows: u64,
65
66 pub max_reoptimizations: usize,
68}
69
70impl Default for AdaptiveConfig {
71 fn default() -> Self {
72 Self {
73 enabled: true,
74 threshold: 3.0,
75 min_rows: 1000,
76 max_reoptimizations: 3,
77 }
78 }
79}
80
81impl AdaptiveConfig {
82 #[must_use]
84 pub fn disabled() -> Self {
85 Self {
86 enabled: false,
87 ..Default::default()
88 }
89 }
90
91 #[must_use]
93 pub fn with_threshold(mut self, threshold: f64) -> Self {
94 self.threshold = threshold;
95 self
96 }
97
98 #[must_use]
100 pub fn with_min_rows(mut self, min_rows: u64) -> Self {
101 self.min_rows = min_rows;
102 self
103 }
104
105 #[must_use]
107 pub fn with_max_reoptimizations(mut self, max: usize) -> Self {
108 self.max_reoptimizations = max;
109 self
110 }
111}
112
113impl Default for Config {
114 fn default() -> Self {
115 Self {
116 path: None,
117 memory_limit: None,
118 spill_path: None,
119 threads: num_cpus::get(),
120 wal_enabled: true,
121 wal_flush_interval_ms: 100,
122 backward_edges: true,
123 query_logging: false,
124 adaptive: AdaptiveConfig::default(),
125 factorized_execution: true,
126 }
127 }
128}
129
130impl Config {
131 #[must_use]
133 pub fn in_memory() -> Self {
134 Self {
135 path: None,
136 wal_enabled: false,
137 ..Default::default()
138 }
139 }
140
141 #[must_use]
143 pub fn persistent(path: impl Into<PathBuf>) -> Self {
144 Self {
145 path: Some(path.into()),
146 wal_enabled: true,
147 ..Default::default()
148 }
149 }
150
151 #[must_use]
153 pub fn with_memory_limit(mut self, limit: usize) -> Self {
154 self.memory_limit = Some(limit);
155 self
156 }
157
158 #[must_use]
160 pub fn with_threads(mut self, threads: usize) -> Self {
161 self.threads = threads;
162 self
163 }
164
165 #[must_use]
167 pub fn without_backward_edges(mut self) -> Self {
168 self.backward_edges = false;
169 self
170 }
171
172 #[must_use]
174 pub fn with_query_logging(mut self) -> Self {
175 self.query_logging = true;
176 self
177 }
178
179 #[must_use]
181 pub fn with_memory_fraction(mut self, fraction: f64) -> Self {
182 use grafeo_common::memory::buffer::BufferManagerConfig;
183 let system_memory = BufferManagerConfig::detect_system_memory();
184 self.memory_limit = Some((system_memory as f64 * fraction) as usize);
185 self
186 }
187
188 #[must_use]
190 pub fn with_spill_path(mut self, path: impl Into<PathBuf>) -> Self {
191 self.spill_path = Some(path.into());
192 self
193 }
194
195 #[must_use]
197 pub fn with_adaptive(mut self, adaptive: AdaptiveConfig) -> Self {
198 self.adaptive = adaptive;
199 self
200 }
201
202 #[must_use]
204 pub fn without_adaptive(mut self) -> Self {
205 self.adaptive.enabled = false;
206 self
207 }
208
209 #[must_use]
215 pub fn without_factorized_execution(mut self) -> Self {
216 self.factorized_execution = false;
217 self
218 }
219}
220
221mod num_cpus {
223 #[cfg(not(target_arch = "wasm32"))]
224 pub fn get() -> usize {
225 std::thread::available_parallelism()
226 .map(|n| n.get())
227 .unwrap_or(4)
228 }
229
230 #[cfg(target_arch = "wasm32")]
231 pub fn get() -> usize {
232 1
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn test_config_default() {
242 let config = Config::default();
243 assert!(config.path.is_none());
244 assert!(config.memory_limit.is_none());
245 assert!(config.spill_path.is_none());
246 assert!(config.threads > 0);
247 assert!(config.wal_enabled);
248 assert_eq!(config.wal_flush_interval_ms, 100);
249 assert!(config.backward_edges);
250 assert!(!config.query_logging);
251 assert!(config.factorized_execution);
252 }
253
254 #[test]
255 fn test_config_in_memory() {
256 let config = Config::in_memory();
257 assert!(config.path.is_none());
258 assert!(!config.wal_enabled);
259 assert!(config.backward_edges);
260 }
261
262 #[test]
263 fn test_config_persistent() {
264 let config = Config::persistent("/tmp/test_db");
265 assert_eq!(
266 config.path.as_deref(),
267 Some(std::path::Path::new("/tmp/test_db"))
268 );
269 assert!(config.wal_enabled);
270 }
271
272 #[test]
273 fn test_config_with_memory_limit() {
274 let config = Config::in_memory().with_memory_limit(1024 * 1024);
275 assert_eq!(config.memory_limit, Some(1024 * 1024));
276 }
277
278 #[test]
279 fn test_config_with_threads() {
280 let config = Config::in_memory().with_threads(8);
281 assert_eq!(config.threads, 8);
282 }
283
284 #[test]
285 fn test_config_without_backward_edges() {
286 let config = Config::in_memory().without_backward_edges();
287 assert!(!config.backward_edges);
288 }
289
290 #[test]
291 fn test_config_with_query_logging() {
292 let config = Config::in_memory().with_query_logging();
293 assert!(config.query_logging);
294 }
295
296 #[test]
297 fn test_config_with_spill_path() {
298 let config = Config::in_memory().with_spill_path("/tmp/spill");
299 assert_eq!(
300 config.spill_path.as_deref(),
301 Some(std::path::Path::new("/tmp/spill"))
302 );
303 }
304
305 #[test]
306 fn test_config_with_memory_fraction() {
307 let config = Config::in_memory().with_memory_fraction(0.5);
308 assert!(config.memory_limit.is_some());
309 assert!(config.memory_limit.unwrap() > 0);
310 }
311
312 #[test]
313 fn test_config_with_adaptive() {
314 let adaptive = AdaptiveConfig::default().with_threshold(5.0);
315 let config = Config::in_memory().with_adaptive(adaptive);
316 assert!((config.adaptive.threshold - 5.0).abs() < f64::EPSILON);
317 }
318
319 #[test]
320 fn test_config_without_adaptive() {
321 let config = Config::in_memory().without_adaptive();
322 assert!(!config.adaptive.enabled);
323 }
324
325 #[test]
326 fn test_config_without_factorized_execution() {
327 let config = Config::in_memory().without_factorized_execution();
328 assert!(!config.factorized_execution);
329 }
330
331 #[test]
332 fn test_config_builder_chaining() {
333 let config = Config::persistent("/tmp/db")
334 .with_memory_limit(512 * 1024 * 1024)
335 .with_threads(4)
336 .with_query_logging()
337 .without_backward_edges()
338 .with_spill_path("/tmp/spill");
339
340 assert!(config.path.is_some());
341 assert_eq!(config.memory_limit, Some(512 * 1024 * 1024));
342 assert_eq!(config.threads, 4);
343 assert!(config.query_logging);
344 assert!(!config.backward_edges);
345 assert!(config.spill_path.is_some());
346 }
347
348 #[test]
349 fn test_adaptive_config_default() {
350 let config = AdaptiveConfig::default();
351 assert!(config.enabled);
352 assert!((config.threshold - 3.0).abs() < f64::EPSILON);
353 assert_eq!(config.min_rows, 1000);
354 assert_eq!(config.max_reoptimizations, 3);
355 }
356
357 #[test]
358 fn test_adaptive_config_disabled() {
359 let config = AdaptiveConfig::disabled();
360 assert!(!config.enabled);
361 }
362
363 #[test]
364 fn test_adaptive_config_with_threshold() {
365 let config = AdaptiveConfig::default().with_threshold(10.0);
366 assert!((config.threshold - 10.0).abs() < f64::EPSILON);
367 }
368
369 #[test]
370 fn test_adaptive_config_with_min_rows() {
371 let config = AdaptiveConfig::default().with_min_rows(500);
372 assert_eq!(config.min_rows, 500);
373 }
374
375 #[test]
376 fn test_adaptive_config_with_max_reoptimizations() {
377 let config = AdaptiveConfig::default().with_max_reoptimizations(5);
378 assert_eq!(config.max_reoptimizations, 5);
379 }
380
381 #[test]
382 fn test_adaptive_config_builder_chaining() {
383 let config = AdaptiveConfig::default()
384 .with_threshold(2.0)
385 .with_min_rows(100)
386 .with_max_reoptimizations(10);
387 assert!((config.threshold - 2.0).abs() < f64::EPSILON);
388 assert_eq!(config.min_rows, 100);
389 assert_eq!(config.max_reoptimizations, 10);
390 }
391}