rust_rule_engine/backward/
optimizer.rs1use super::goal::Goal;
25use std::collections::HashMap;
26
27#[derive(Debug, Clone)]
29pub struct QueryOptimizer {
30 selectivity_map: HashMap<String, f64>,
32
33 enable_reordering: bool,
35
36 enable_index_selection: bool,
38
39 enable_memoization: bool,
41
42 stats: OptimizationStats,
44}
45
46impl QueryOptimizer {
47 pub fn new() -> Self {
49 Self {
50 selectivity_map: HashMap::new(),
51 enable_reordering: true,
52 enable_index_selection: true,
53 enable_memoization: true,
54 stats: OptimizationStats::new(),
55 }
56 }
57
58 pub fn with_config(config: OptimizerConfig) -> Self {
60 Self {
61 selectivity_map: HashMap::new(),
62 enable_reordering: config.enable_reordering,
63 enable_index_selection: config.enable_index_selection,
64 enable_memoization: config.enable_memoization,
65 stats: OptimizationStats::new(),
66 }
67 }
68
69 pub fn optimize_goals(&mut self, goals: Vec<Goal>) -> Vec<Goal> {
73 if !self.enable_reordering || goals.len() <= 1 {
74 return goals;
75 }
76
77 self.stats.total_optimizations += 1;
78
79 let mut goal_selectivity: Vec<(Goal, f64)> = goals
81 .into_iter()
82 .map(|g| {
83 let selectivity = self.estimate_selectivity(&g);
84 (g, selectivity)
85 })
86 .collect();
87
88 goal_selectivity.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
90
91 let optimized: Vec<Goal> = goal_selectivity.into_iter().map(|(g, _)| g).collect();
93
94 self.stats.goals_reordered += optimized.len();
95
96 optimized
97 }
98
99 pub fn estimate_selectivity(&self, goal: &Goal) -> f64 {
103 if let Some(&selectivity) = self.selectivity_map.get(&goal.pattern) {
105 return selectivity;
106 }
107
108 self.heuristic_selectivity(goal)
110 }
111
112 fn heuristic_selectivity(&self, goal: &Goal) -> f64 {
114 let pattern = &goal.pattern;
115
116 let (bound_count, var_count) = self.count_variables(pattern);
118
119 if var_count == 0 {
120 return 0.1;
122 }
123
124 let bound_ratio = bound_count as f64 / var_count as f64;
126 let selectivity = 1.0 - (bound_ratio * 0.8);
127
128 if pattern.contains("in_stock") || pattern.contains("available") {
130 return selectivity * 0.3;
132 }
133
134 if pattern.contains("expensive") || pattern.contains("premium") {
135 return selectivity * 0.5;
137 }
138
139 if pattern.contains("item") || pattern.contains("product") {
140 return selectivity * 1.2;
142 }
143
144 selectivity
145 }
146
147 fn count_variables(&self, pattern: &str) -> (usize, usize) {
149 let mut bound = 0;
150 let mut total = 0;
151
152 let chars: Vec<char> = pattern.chars().collect();
153 let mut i = 0;
154
155 while i < chars.len() {
156 if chars[i] == '?' {
157 total += 1;
158
159 i += 1;
161 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
162 i += 1;
163 }
164
165 while i < chars.len() && chars[i].is_whitespace() {
167 i += 1;
168 }
169
170 if i < chars.len() && (chars[i] == '=' || chars[i] == '>' || chars[i] == '<') {
171 bound += 1;
172 }
173 } else {
174 i += 1;
175 }
176 }
177
178 (bound, total)
179 }
180
181 pub fn set_selectivity(&mut self, predicate: String, selectivity: f64) {
183 self.selectivity_map.insert(predicate, selectivity.clamp(0.0, 1.0));
184 }
185
186 pub fn stats(&self) -> &OptimizationStats {
188 &self.stats
189 }
190
191 pub fn reset_stats(&mut self) {
193 self.stats = OptimizationStats::new();
194 }
195
196 pub fn set_reordering(&mut self, enabled: bool) {
198 self.enable_reordering = enabled;
199 }
200
201 pub fn set_index_selection(&mut self, enabled: bool) {
203 self.enable_index_selection = enabled;
204 }
205
206 pub fn set_memoization(&mut self, enabled: bool) {
208 self.enable_memoization = enabled;
209 }
210}
211
212impl Default for QueryOptimizer {
213 fn default() -> Self {
214 Self::new()
215 }
216}
217
218#[derive(Debug, Clone)]
220pub struct OptimizerConfig {
221 pub enable_reordering: bool,
223
224 pub enable_index_selection: bool,
226
227 pub enable_memoization: bool,
229}
230
231impl Default for OptimizerConfig {
232 fn default() -> Self {
233 Self {
234 enable_reordering: true,
235 enable_index_selection: true,
236 enable_memoization: true,
237 }
238 }
239}
240
241#[derive(Debug, Clone, Default)]
243pub struct OptimizationStats {
244 pub total_optimizations: usize,
246
247 pub goals_reordered: usize,
249
250 pub index_selections: usize,
252
253 pub memoization_hits: usize,
255
256 pub memoization_misses: usize,
258}
259
260impl OptimizationStats {
261 pub fn new() -> Self {
263 Self::default()
264 }
265
266 pub fn memoization_hit_rate(&self) -> f64 {
268 let total = self.memoization_hits + self.memoization_misses;
269 if total == 0 {
270 0.0
271 } else {
272 self.memoization_hits as f64 / total as f64
273 }
274 }
275
276 pub fn summary(&self) -> String {
278 format!(
279 "Optimizations: {} | Goals reordered: {} | Memo hits: {} ({:.1}%)",
280 self.total_optimizations,
281 self.goals_reordered,
282 self.memoization_hits,
283 self.memoization_hit_rate() * 100.0
284 )
285 }
286}
287
288#[derive(Debug, Clone)]
290pub struct JoinOptimizer {
291 cost_model: HashMap<String, f64>,
293}
294
295impl JoinOptimizer {
296 pub fn new() -> Self {
298 Self {
299 cost_model: HashMap::new(),
300 }
301 }
302
303 pub fn optimize_joins(&self, goals: Vec<Goal>) -> Vec<Goal> {
307 if goals.len() <= 1 {
308 return goals;
309 }
310
311 let mut sorted_goals = goals;
313 sorted_goals.sort_by_key(|g| {
314 -(self.count_bound_vars(&g.pattern) as i32)
316 });
317
318 sorted_goals
319 }
320
321 fn count_bound_vars(&self, pattern: &str) -> usize {
323 let mut count = 0;
325 let chars: Vec<char> = pattern.chars().collect();
326 let mut i = 0;
327
328 while i < chars.len() {
329 if chars[i] == '?' {
330 i += 1;
332 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
333 i += 1;
334 }
335
336 while i < chars.len() && chars[i].is_whitespace() {
338 i += 1;
339 }
340
341 if i < chars.len() && (chars[i] == '=' || chars[i] == '>' || chars[i] == '<') {
342 count += 1;
343 }
344 } else {
345 i += 1;
346 }
347 }
348
349 count
350 }
351}
352
353impl Default for JoinOptimizer {
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_optimizer_creation() {
365 let optimizer = QueryOptimizer::new();
366 assert!(optimizer.enable_reordering);
367 assert!(optimizer.enable_index_selection);
368 assert!(optimizer.enable_memoization);
369 }
370
371 #[test]
372 fn test_optimizer_with_config() {
373 let config = OptimizerConfig {
374 enable_reordering: false,
375 enable_index_selection: true,
376 enable_memoization: false,
377 };
378
379 let optimizer = QueryOptimizer::with_config(config);
380 assert!(!optimizer.enable_reordering);
381 assert!(optimizer.enable_index_selection);
382 assert!(!optimizer.enable_memoization);
383 }
384
385 #[test]
386 fn test_goal_reordering() {
387 let mut optimizer = QueryOptimizer::new();
388
389 optimizer.set_selectivity("in_stock(?x)".to_string(), 0.1);
391 optimizer.set_selectivity("expensive(?x)".to_string(), 0.3);
392 optimizer.set_selectivity("item(?x)".to_string(), 0.9);
393
394 let goals = vec![
395 Goal::new("item(?x)".to_string()),
396 Goal::new("expensive(?x)".to_string()),
397 Goal::new("in_stock(?x)".to_string()),
398 ];
399
400 let optimized = optimizer.optimize_goals(goals);
401
402 assert_eq!(optimized[0].pattern, "in_stock(?x)");
404 assert_eq!(optimized[1].pattern, "expensive(?x)");
405 assert_eq!(optimized[2].pattern, "item(?x)");
406 }
407
408 #[test]
409 fn test_selectivity_estimation() {
410 let optimizer = QueryOptimizer::new();
411
412 let goal1 = Goal::new("employee(alice)".to_string());
414 let sel1 = optimizer.estimate_selectivity(&goal1);
415 assert!(sel1 < 0.5);
416
417 let goal2 = Goal::new("employee(?x)".to_string());
419 let sel2 = optimizer.estimate_selectivity(&goal2);
420 assert!(sel2 > sel1);
421
422 let goal3 = Goal::new("salary(?x) WHERE ?x > 100000".to_string());
424 let sel3 = optimizer.estimate_selectivity(&goal3);
425 assert!(sel3 < sel2);
427 }
428
429 #[test]
430 fn test_count_variables() {
431 let optimizer = QueryOptimizer::new();
432
433 let (bound, total) = optimizer.count_variables("employee(alice)");
435 assert_eq!(total, 0);
436 assert_eq!(bound, 0);
437
438 let (bound, total) = optimizer.count_variables("employee(?x)");
440 assert_eq!(total, 1);
441 assert_eq!(bound, 0);
442
443 let (bound, total) = optimizer.count_variables("salary(?x) WHERE ?x > 100");
445 assert_eq!(total, 2); assert_eq!(bound, 1); }
448
449 #[test]
450 fn test_optimization_stats() {
451 let mut optimizer = QueryOptimizer::new();
452
453 let goals = vec![
454 Goal::new("a(?x)".to_string()),
455 Goal::new("b(?x)".to_string()),
456 ];
457
458 optimizer.optimize_goals(goals);
459
460 let stats = optimizer.stats();
461 assert_eq!(stats.total_optimizations, 1);
462 assert_eq!(stats.goals_reordered, 2);
463 }
464
465 #[test]
466 fn test_join_optimizer() {
467 let optimizer = JoinOptimizer::new();
468
469 let goals = vec![
470 Goal::new("item(?x)".to_string()),
471 Goal::new("price(?x, ?p) WHERE ?p > 100".to_string()),
472 Goal::new("in_stock(?x)".to_string()),
473 ];
474
475 let optimized = optimizer.optimize_joins(goals);
476
477 assert!(optimized[0].pattern.contains("?p > 100"));
479 }
480
481 #[test]
482 fn test_disable_reordering() {
483 let mut optimizer = QueryOptimizer::new();
484 optimizer.set_reordering(false);
485
486 let goals = vec![
487 Goal::new("a(?x)".to_string()),
488 Goal::new("b(?x)".to_string()),
489 Goal::new("c(?x)".to_string()),
490 ];
491
492 let optimized = optimizer.optimize_goals(goals.clone());
493
494 assert_eq!(optimized[0].pattern, goals[0].pattern);
496 assert_eq!(optimized[1].pattern, goals[1].pattern);
497 assert_eq!(optimized[2].pattern, goals[2].pattern);
498 }
499
500 #[test]
501 fn test_stats_summary() {
502 let mut stats = OptimizationStats::new();
503 stats.total_optimizations = 10;
504 stats.goals_reordered = 25;
505 stats.memoization_hits = 8;
506 stats.memoization_misses = 2;
507
508 let summary = stats.summary();
509 assert!(summary.contains("10"));
510 assert!(summary.contains("25"));
511 assert!(summary.contains("8"));
512 assert!(summary.contains("80")); }
514
515 #[test]
516 fn test_memoization_hit_rate() {
517 let mut stats = OptimizationStats::new();
518
519 assert_eq!(stats.memoization_hit_rate(), 0.0);
521
522 stats.memoization_hits = 8;
524 stats.memoization_misses = 2;
525 assert!((stats.memoization_hit_rate() - 0.8).abs() < 0.01);
526
527 stats.memoization_hits = 10;
529 stats.memoization_misses = 0;
530 assert_eq!(stats.memoization_hit_rate(), 1.0);
531 }
532}