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 pub fn get() -> usize {
224 std::thread::available_parallelism()
225 .map(|n| n.get())
226 .unwrap_or(4)
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_config_default() {
236 let config = Config::default();
237 assert!(config.path.is_none());
238 assert!(config.memory_limit.is_none());
239 assert!(config.spill_path.is_none());
240 assert!(config.threads > 0);
241 assert!(config.wal_enabled);
242 assert_eq!(config.wal_flush_interval_ms, 100);
243 assert!(config.backward_edges);
244 assert!(!config.query_logging);
245 assert!(config.factorized_execution);
246 }
247
248 #[test]
249 fn test_config_in_memory() {
250 let config = Config::in_memory();
251 assert!(config.path.is_none());
252 assert!(!config.wal_enabled);
253 assert!(config.backward_edges);
254 }
255
256 #[test]
257 fn test_config_persistent() {
258 let config = Config::persistent("/tmp/test_db");
259 assert_eq!(
260 config.path.as_deref(),
261 Some(std::path::Path::new("/tmp/test_db"))
262 );
263 assert!(config.wal_enabled);
264 }
265
266 #[test]
267 fn test_config_with_memory_limit() {
268 let config = Config::in_memory().with_memory_limit(1024 * 1024);
269 assert_eq!(config.memory_limit, Some(1024 * 1024));
270 }
271
272 #[test]
273 fn test_config_with_threads() {
274 let config = Config::in_memory().with_threads(8);
275 assert_eq!(config.threads, 8);
276 }
277
278 #[test]
279 fn test_config_without_backward_edges() {
280 let config = Config::in_memory().without_backward_edges();
281 assert!(!config.backward_edges);
282 }
283
284 #[test]
285 fn test_config_with_query_logging() {
286 let config = Config::in_memory().with_query_logging();
287 assert!(config.query_logging);
288 }
289
290 #[test]
291 fn test_config_with_spill_path() {
292 let config = Config::in_memory().with_spill_path("/tmp/spill");
293 assert_eq!(
294 config.spill_path.as_deref(),
295 Some(std::path::Path::new("/tmp/spill"))
296 );
297 }
298
299 #[test]
300 fn test_config_with_memory_fraction() {
301 let config = Config::in_memory().with_memory_fraction(0.5);
302 assert!(config.memory_limit.is_some());
303 assert!(config.memory_limit.unwrap() > 0);
304 }
305
306 #[test]
307 fn test_config_with_adaptive() {
308 let adaptive = AdaptiveConfig::default().with_threshold(5.0);
309 let config = Config::in_memory().with_adaptive(adaptive);
310 assert!((config.adaptive.threshold - 5.0).abs() < f64::EPSILON);
311 }
312
313 #[test]
314 fn test_config_without_adaptive() {
315 let config = Config::in_memory().without_adaptive();
316 assert!(!config.adaptive.enabled);
317 }
318
319 #[test]
320 fn test_config_without_factorized_execution() {
321 let config = Config::in_memory().without_factorized_execution();
322 assert!(!config.factorized_execution);
323 }
324
325 #[test]
326 fn test_config_builder_chaining() {
327 let config = Config::persistent("/tmp/db")
328 .with_memory_limit(512 * 1024 * 1024)
329 .with_threads(4)
330 .with_query_logging()
331 .without_backward_edges()
332 .with_spill_path("/tmp/spill");
333
334 assert!(config.path.is_some());
335 assert_eq!(config.memory_limit, Some(512 * 1024 * 1024));
336 assert_eq!(config.threads, 4);
337 assert!(config.query_logging);
338 assert!(!config.backward_edges);
339 assert!(config.spill_path.is_some());
340 }
341
342 #[test]
343 fn test_adaptive_config_default() {
344 let config = AdaptiveConfig::default();
345 assert!(config.enabled);
346 assert!((config.threshold - 3.0).abs() < f64::EPSILON);
347 assert_eq!(config.min_rows, 1000);
348 assert_eq!(config.max_reoptimizations, 3);
349 }
350
351 #[test]
352 fn test_adaptive_config_disabled() {
353 let config = AdaptiveConfig::disabled();
354 assert!(!config.enabled);
355 }
356
357 #[test]
358 fn test_adaptive_config_with_threshold() {
359 let config = AdaptiveConfig::default().with_threshold(10.0);
360 assert!((config.threshold - 10.0).abs() < f64::EPSILON);
361 }
362
363 #[test]
364 fn test_adaptive_config_with_min_rows() {
365 let config = AdaptiveConfig::default().with_min_rows(500);
366 assert_eq!(config.min_rows, 500);
367 }
368
369 #[test]
370 fn test_adaptive_config_with_max_reoptimizations() {
371 let config = AdaptiveConfig::default().with_max_reoptimizations(5);
372 assert_eq!(config.max_reoptimizations, 5);
373 }
374
375 #[test]
376 fn test_adaptive_config_builder_chaining() {
377 let config = AdaptiveConfig::default()
378 .with_threshold(2.0)
379 .with_min_rows(100)
380 .with_max_reoptimizations(10);
381 assert!((config.threshold - 2.0).abs() < f64::EPSILON);
382 assert_eq!(config.min_rows, 100);
383 assert_eq!(config.max_reoptimizations, 10);
384 }
385}