1use crate::query_execution::{ExecutionPlan, ExecutionStrategy, QueryCost};
2use crate::statistics::ColumnStatistics;
3use crate::indexing::{IndexType, IndexSelector};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
8pub struct CostEstimator {
9 row_count: u64,
10 column_count: usize,
11 total_size_estimate: f64,
12 column_stats: HashMap<String, ColumnStatistics>,
13}
14
15impl CostEstimator {
16 pub fn new(
18 row_count: u64,
19 column_count: usize,
20 total_size_estimate: f64,
21 column_stats: HashMap<String, ColumnStatistics>,
22 ) -> Self {
23 Self {
24 row_count,
25 column_count,
26 total_size_estimate,
27 column_stats,
28 }
29 }
30
31 pub fn estimate_full_scan(&self) -> QueryCost {
33 let io_cost = self.total_size_estimate / 1_000_000.0;
34 let cpu_cost = (self.row_count as f64) / 10_000.0;
35 let memory_cost = (self.column_count as f64) * 1000.0;
36
37 QueryCost::new(io_cost, cpu_cost, memory_cost, self.row_count)
38 }
39
40 pub fn estimate_predicate_pushdown(&self, selectivity: f64) -> QueryCost {
42 let io_cost = self.total_size_estimate / 1_000_000.0;
43 let filtered_rows = (self.row_count as f64 * selectivity).max(1.0) as u64;
44 let cpu_cost = (self.row_count as f64) / 5_000.0; let memory_cost = (filtered_rows as f64) / 100.0;
46
47 QueryCost::new(io_cost, cpu_cost, memory_cost, filtered_rows)
48 }
49
50 pub fn estimate_column_pruning(&self, num_columns: usize) -> QueryCost {
52 let column_fraction = (num_columns as f64) / (self.column_count as f64);
53 let io_cost = self.total_size_estimate * column_fraction / 1_000_000.0;
54 let cpu_cost = (self.row_count as f64) / 15_000.0;
55 let memory_cost = (num_columns as f64) * 500.0;
56
57 QueryCost::new(io_cost, cpu_cost, memory_cost, self.row_count)
58 }
59
60 pub fn estimate_index_lookup(&self, cardinality: usize, index_type: IndexType) -> QueryCost {
62 let cardinality_ratio = (cardinality as f64) / (self.row_count as f64);
63 let speedup = IndexSelector::estimated_speedup(index_type, cardinality_ratio);
64
65 let io_cost = ((self.total_size_estimate / 1_000_000.0) / speedup).max(0.1);
66 let cpu_cost = (50.0) / speedup; let memory_cost = 100.0; QueryCost::new(io_cost, cpu_cost, memory_cost, cardinality as u64)
70 }
71
72 pub fn estimate_cached_query(&self) -> QueryCost {
74 QueryCost::new(0.1, 0.01, 10.0, self.row_count)
75 .with_cache_probability(0.95)
76 }
77}
78
79#[derive(Debug, Clone)]
81pub struct CandidatePlan {
82 pub plan: ExecutionPlan,
83 pub cost: f64,
84 pub speedup_estimate: f64,
85 pub rank: usize,
86}
87
88impl CandidatePlan {
89 pub fn new(plan: ExecutionPlan, cost: f64) -> Self {
91 Self {
92 plan,
93 cost,
94 speedup_estimate: 1.0,
95 rank: 0,
96 }
97 }
98
99 pub fn with_speedup(mut self, baseline_cost: f64) -> Self {
101 self.speedup_estimate = baseline_cost / self.cost.max(0.01);
102 self
103 }
104
105 pub fn with_rank(mut self, rank: usize) -> Self {
107 self.rank = rank;
108 self
109 }
110}
111
112#[derive(Debug, Clone)]
114pub struct SelectivityEstimator {
115 column_stats: HashMap<String, ColumnStatistics>,
116}
117
118impl SelectivityEstimator {
119 pub fn new(column_stats: HashMap<String, ColumnStatistics>) -> Self {
121 Self { column_stats }
122 }
123
124 pub fn estimate_equality(&self, column: &str) -> f64 {
126 if let Some(stats) = self.column_stats.get(column) {
127 1.0 / (stats.distinct_count as f64).max(1.0)
128 } else {
129 0.1 }
131 }
132
133 pub fn estimate_range(&self, column: &str, _min: &str, _max: &str) -> f64 {
135 if let Some(stats) = self.column_stats.get(column) {
136 (stats.null_count as f64) / (stats.row_count as f64).max(1.0)
137 } else {
138 0.5 }
140 }
141
142 pub fn estimate_in(&self, column: &str, num_values: usize) -> f64 {
144 if let Some(stats) = self.column_stats.get(column) {
145 let per_value = 1.0 / (stats.distinct_count as f64).max(1.0);
146 (per_value * num_values as f64).min(1.0)
147 } else {
148 0.1 * num_values as f64
149 }
150 }
151
152 pub fn estimate_combined(&self, selectivities: Vec<f64>) -> f64 {
154 selectivities.iter().product()
155 }
156}
157
158#[derive(Debug, Clone)]
160pub struct PlanGenerator {
161 cost_estimator: CostEstimator,
162 selectivity_estimator: SelectivityEstimator,
163}
164
165impl PlanGenerator {
166 pub fn new(
168 cost_estimator: CostEstimator,
169 selectivity_estimator: SelectivityEstimator,
170 ) -> Self {
171 Self {
172 cost_estimator,
173 selectivity_estimator,
174 }
175 }
176
177 pub fn generate_plans(&self, num_columns: usize, has_predicates: bool) -> Vec<CandidatePlan> {
179 let mut plans = Vec::new();
180
181 let baseline_cost = self.cost_estimator.estimate_full_scan();
183 let baseline_total = baseline_cost.total_cost();
184 let full_scan_plan = ExecutionPlan::new(
185 ExecutionStrategy::FullTableScan,
186 baseline_cost.clone(),
187 );
188 plans.push(
189 CandidatePlan::new(full_scan_plan, baseline_total)
190 .with_rank(1)
191 );
192
193 if num_columns > 0 && num_columns < 10 {
195 let pruned_cost = self.cost_estimator.estimate_column_pruning(num_columns);
196 let pruned_total = pruned_cost.total_cost();
197 let column_plan = ExecutionPlan::new(
198 ExecutionStrategy::ColumnPruning,
199 pruned_cost,
200 );
201 plans.push(
202 CandidatePlan::new(column_plan, pruned_total)
203 .with_speedup(baseline_total)
204 .with_rank(2)
205 );
206 }
207
208 if has_predicates {
210 let selectivity = 0.3; let pred_cost = self.cost_estimator.estimate_predicate_pushdown(selectivity);
212 let pred_total = pred_cost.total_cost();
213 let pred_plan = ExecutionPlan::new(
214 ExecutionStrategy::PredicatePushdown,
215 pred_cost,
216 );
217 plans.push(
218 CandidatePlan::new(pred_plan, pred_total)
219 .with_speedup(baseline_total)
220 .with_rank(3)
221 );
222 }
223
224 if num_columns > 0 && has_predicates {
226 let selectivity = 0.3;
227 let combined_io = (baseline_total * 0.3 * 0.5) / 20.0; let combined_cpu = (baseline_total * 0.3) / 20.0;
229 let combined_cost = QueryCost::new(
230 combined_io * 20.0,
231 combined_cpu * 20.0,
232 100.0,
233 (self.cost_estimator.row_count as f64 * selectivity) as u64,
234 );
235 let combined_total = combined_cost.total_cost();
236 let combined_plan = ExecutionPlan::new(
237 ExecutionStrategy::Combined,
238 combined_cost,
239 );
240 plans.push(
241 CandidatePlan::new(combined_plan, combined_total)
242 .with_speedup(baseline_total)
243 .with_rank(4)
244 );
245 }
246
247 let cached_cost = self.cost_estimator.estimate_cached_query();
249 let cached_total = cached_cost.total_cost();
250 let cached_plan = ExecutionPlan::new(
251 ExecutionStrategy::CacheHit,
252 cached_cost,
253 );
254 plans.push(
255 CandidatePlan::new(cached_plan, cached_total)
256 .with_speedup(baseline_total)
257 .with_rank(5)
258 );
259
260 plans.sort_by(|a, b| a.cost.partial_cmp(&b.cost).unwrap_or(std::cmp::Ordering::Equal));
262
263 plans
264 }
265}
266
267#[derive(Debug, Clone)]
269pub struct MultiIndexCoordinator {
270 available_indices: HashMap<String, IndexType>,
271}
272
273impl MultiIndexCoordinator {
274 pub fn new() -> Self {
276 Self {
277 available_indices: HashMap::new(),
278 }
279 }
280
281 pub fn register_index(&mut self, column: String, index_type: IndexType) {
283 self.available_indices.insert(column, index_type);
284 }
285
286 pub fn find_best_index(&self, column: &str) -> Option<IndexType> {
288 self.available_indices.get(column).cloned()
289 }
290
291 pub fn registered_columns(&self) -> Vec<String> {
293 self.available_indices.keys().cloned().collect()
294 }
295
296 pub fn index_count(&self) -> usize {
298 self.available_indices.len()
299 }
300}
301
302impl Default for MultiIndexCoordinator {
303 fn default() -> Self {
304 Self::new()
305 }
306}
307
308#[derive(Debug, Clone)]
310pub struct PlanEvaluator {
311 baseline_cost: f64,
312}
313
314impl PlanEvaluator {
315 pub fn new(baseline_cost: f64) -> Self {
317 Self { baseline_cost }
318 }
319
320 pub fn evaluate_plans(&self, mut plans: Vec<CandidatePlan>) -> Vec<CandidatePlan> {
322 for plan in &mut plans {
324 plan.speedup_estimate = self.baseline_cost / plan.cost.max(0.01);
325 }
326
327 plans.sort_by(|a, b| {
329 b.speedup_estimate.partial_cmp(&a.speedup_estimate)
330 .unwrap_or(std::cmp::Ordering::Equal)
331 });
332
333 for (idx, plan) in plans.iter_mut().enumerate() {
335 plan.rank = idx + 1;
336 }
337
338 plans
339 }
340
341 pub fn best_plan<'a>(&self, plans: &'a [CandidatePlan]) -> Option<&'a CandidatePlan> {
343 plans.iter().min_by(|a, b| {
344 a.cost.partial_cmp(&b.cost).unwrap_or(std::cmp::Ordering::Equal)
345 })
346 }
347
348 pub fn improvement_percentage(&self, plan: &CandidatePlan) -> f64 {
350 ((self.baseline_cost - plan.cost) / self.baseline_cost) * 100.0
351 }
352}
353
354#[derive(Debug, Clone)]
356pub struct AdvancedQueryOptimizer {
357 cost_estimator: CostEstimator,
358 selectivity_estimator: SelectivityEstimator,
359 plan_generator: PlanGenerator,
360 plan_evaluator: PlanEvaluator,
361 index_coordinator: MultiIndexCoordinator,
362}
363
364impl AdvancedQueryOptimizer {
365 pub fn new(
367 row_count: u64,
368 column_count: usize,
369 total_size_estimate: f64,
370 column_stats: HashMap<String, ColumnStatistics>,
371 ) -> Self {
372 let cost_estimator = CostEstimator::new(row_count, column_count, total_size_estimate, column_stats.clone());
373 let selectivity_estimator = SelectivityEstimator::new(column_stats);
374 let baseline = cost_estimator.estimate_full_scan();
375 let baseline_cost = baseline.total_cost();
376 let plan_generator = PlanGenerator::new(cost_estimator.clone(), selectivity_estimator.clone());
377 let plan_evaluator = PlanEvaluator::new(baseline_cost);
378
379 Self {
380 cost_estimator,
381 selectivity_estimator,
382 plan_generator,
383 plan_evaluator,
384 index_coordinator: MultiIndexCoordinator::new(),
385 }
386 }
387
388 pub fn optimize_query(&self, num_columns: usize, has_predicates: bool) -> Option<CandidatePlan> {
390 let candidate_plans = self.plan_generator.generate_plans(num_columns, has_predicates);
391 let evaluated_plans = self.plan_evaluator.evaluate_plans(candidate_plans);
392 evaluated_plans.first().cloned()
393 }
394
395 pub fn get_top_plans(&self, num_columns: usize, has_predicates: bool, n: usize) -> Vec<CandidatePlan> {
397 let candidate_plans = self.plan_generator.generate_plans(num_columns, has_predicates);
398 let evaluated_plans = self.plan_evaluator.evaluate_plans(candidate_plans);
399 evaluated_plans.into_iter().take(n).collect()
400 }
401
402 pub fn register_index(&mut self, column: String, index_type: IndexType) {
404 self.index_coordinator.register_index(column, index_type);
405 }
406
407 pub fn index_coordinator(&self) -> &MultiIndexCoordinator {
409 &self.index_coordinator
410 }
411
412 pub fn get_selectivity(&self, column: &str) -> f64 {
414 self.selectivity_estimator.estimate_equality(column)
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 fn create_test_stats() -> (u64, usize, f64, HashMap<String, ColumnStatistics>) {
423 let row_count = 1_000_000_u64;
424 let column_count = 10_usize;
425 let total_size = 100_000_000.0; let mut column_stats = HashMap::new();
428 for i in 0..10 {
429 column_stats.insert(
430 format!("col_{}", i),
431 ColumnStatistics::new(
432 format!("col_{}", i),
433 "String".to_string(),
434 row_count,
435 1000,
436 1000 + (i as u64 * 100),
437 ),
438 );
439 }
440
441 (row_count, column_count, total_size, column_stats)
442 }
443
444 #[test]
445 fn test_cost_estimator_creation() {
446 let (row_count, column_count, total_size, column_stats) = create_test_stats();
447 let estimator = CostEstimator::new(row_count, column_count, total_size, column_stats);
448 assert_eq!(estimator.row_count, 1_000_000);
449 }
450
451 #[test]
452 fn test_estimate_full_scan() {
453 let (row_count, column_count, total_size, column_stats) = create_test_stats();
454 let estimator = CostEstimator::new(row_count, column_count, total_size, column_stats);
455 let cost = estimator.estimate_full_scan();
456 assert!(cost.io_cost > 0.0);
457 assert!(cost.total_cost() > 0.0);
458 }
459
460 #[test]
461 fn test_estimate_predicate_pushdown() {
462 let (row_count, column_count, total_size, column_stats) = create_test_stats();
463 let estimator = CostEstimator::new(row_count, column_count, total_size, column_stats);
464 let cost = estimator.estimate_predicate_pushdown(0.3);
465 assert!(cost.estimated_rows <= 1_000_000);
466 }
467
468 #[test]
469 fn test_estimate_column_pruning() {
470 let (row_count, column_count, total_size, column_stats) = create_test_stats();
471 let estimator = CostEstimator::new(row_count, column_count, total_size, column_stats);
472 let cost = estimator.estimate_column_pruning(5);
473 assert!(cost.io_cost < 100.0);
474 }
475
476 #[test]
477 fn test_estimate_index_lookup() {
478 let (row_count, column_count, total_size, column_stats) = create_test_stats();
479 let estimator = CostEstimator::new(row_count, column_count, total_size, column_stats);
480 let cost = estimator.estimate_index_lookup(1000, IndexType::Hash);
481 assert!(cost.io_cost < 10.0);
482 }
483
484 #[test]
485 fn test_estimate_cached_query() {
486 let (row_count, column_count, total_size, column_stats) = create_test_stats();
487 let estimator = CostEstimator::new(row_count, column_count, total_size, column_stats);
488 let cost = estimator.estimate_cached_query();
489 assert!(cost.cache_hit_probability > 0.9);
490 }
491
492 #[test]
493 fn test_candidate_plan_creation() {
494 let plan = ExecutionPlan::new(ExecutionStrategy::FullTableScan, QueryCost::new(10.0, 1.0, 100.0, 1000));
495 let candidate = CandidatePlan::new(plan, 50.0);
496 assert_eq!(candidate.cost, 50.0);
497 }
498
499 #[test]
500 fn test_candidate_plan_with_speedup() {
501 let plan = ExecutionPlan::new(ExecutionStrategy::FullTableScan, QueryCost::new(10.0, 1.0, 100.0, 1000));
502 let candidate = CandidatePlan::new(plan, 25.0).with_speedup(100.0);
503 assert_eq!(candidate.speedup_estimate, 4.0);
504 }
505
506 #[test]
507 fn test_selectivity_estimator_equality() {
508 let (_, _, _, column_stats) = create_test_stats();
509 let estimator = SelectivityEstimator::new(column_stats);
510 let selectivity = estimator.estimate_equality("col_0");
511 assert!(selectivity > 0.0 && selectivity < 1.0);
512 }
513
514 #[test]
515 fn test_selectivity_estimator_range() {
516 let (_, _, _, column_stats) = create_test_stats();
517 let estimator = SelectivityEstimator::new(column_stats);
518 let selectivity = estimator.estimate_range("col_0", "0", "100");
519 assert!(selectivity >= 0.0 && selectivity <= 1.0);
520 }
521
522 #[test]
523 fn test_selectivity_estimator_in() {
524 let (_, _, _, column_stats) = create_test_stats();
525 let estimator = SelectivityEstimator::new(column_stats);
526 let selectivity = estimator.estimate_in("col_0", 10);
527 assert!(selectivity >= 0.0 && selectivity <= 1.0);
528 }
529
530 #[test]
531 fn test_selectivity_estimator_combined() {
532 let (_, _, _, column_stats) = create_test_stats();
533 let estimator = SelectivityEstimator::new(column_stats);
534 let combined = estimator.estimate_combined(vec![0.5, 0.3, 0.2]);
535 assert_eq!(combined, 0.03);
536 }
537
538 #[test]
539 fn test_plan_generator_creation() {
540 let (row_count, column_count, total_size, column_stats) = create_test_stats();
541 let cost_estimator = CostEstimator::new(row_count, column_count, total_size, column_stats.clone());
542 let selectivity_estimator = SelectivityEstimator::new(column_stats);
543 let generator = PlanGenerator::new(cost_estimator, selectivity_estimator);
544 assert!(generator.generate_plans(5, false).len() > 0);
545 }
546
547 #[test]
548 fn test_plan_generator_multiple_plans() {
549 let (row_count, column_count, total_size, column_stats) = create_test_stats();
550 let cost_estimator = CostEstimator::new(row_count, column_count, total_size, column_stats.clone());
551 let selectivity_estimator = SelectivityEstimator::new(column_stats);
552 let generator = PlanGenerator::new(cost_estimator, selectivity_estimator);
553 let plans = generator.generate_plans(5, true);
554 assert!(plans.len() >= 3);
555 }
556
557 #[test]
558 fn test_multi_index_coordinator_registration() {
559 let mut coordinator = MultiIndexCoordinator::new();
560 coordinator.register_index("col_1".to_string(), IndexType::Hash);
561 coordinator.register_index("col_2".to_string(), IndexType::BTree);
562 assert_eq!(coordinator.index_count(), 2);
563 }
564
565 #[test]
566 fn test_multi_index_coordinator_find_index() {
567 let mut coordinator = MultiIndexCoordinator::new();
568 coordinator.register_index("col_1".to_string(), IndexType::Hash);
569 let found = coordinator.find_best_index("col_1");
570 assert_eq!(found, Some(IndexType::Hash));
571 }
572
573 #[test]
574 fn test_multi_index_coordinator_registered_columns() {
575 let mut coordinator = MultiIndexCoordinator::new();
576 coordinator.register_index("col_1".to_string(), IndexType::Hash);
577 coordinator.register_index("col_2".to_string(), IndexType::BTree);
578 let columns = coordinator.registered_columns();
579 assert_eq!(columns.len(), 2);
580 }
581
582 #[test]
583 fn test_plan_evaluator_best_plan() {
584 let evaluator = PlanEvaluator::new(100.0);
585 let plan1 = ExecutionPlan::new(ExecutionStrategy::FullTableScan, QueryCost::new(10.0, 1.0, 100.0, 1000));
586 let plan2 = ExecutionPlan::new(ExecutionStrategy::ColumnPruning, QueryCost::new(5.0, 0.5, 50.0, 1000));
587
588 let candidates = vec![
589 CandidatePlan::new(plan1, 50.0),
590 CandidatePlan::new(plan2, 25.0),
591 ];
592
593 let best = evaluator.best_plan(&candidates);
594 assert!(best.is_some());
595 assert_eq!(best.unwrap().cost, 25.0);
596 }
597
598 #[test]
599 fn test_plan_evaluator_improvement_percentage() {
600 let evaluator = PlanEvaluator::new(100.0);
601 let plan = ExecutionPlan::new(ExecutionStrategy::ColumnPruning, QueryCost::new(5.0, 0.5, 50.0, 1000));
602 let candidate = CandidatePlan::new(plan, 50.0);
603
604 let improvement = evaluator.improvement_percentage(&candidate);
605 assert_eq!(improvement, 50.0);
606 }
607
608 #[test]
609 fn test_advanced_optimizer_creation() {
610 let (row_count, column_count, total_size, column_stats) = create_test_stats();
611 let optimizer = AdvancedQueryOptimizer::new(row_count, column_count, total_size, column_stats);
612 assert!(optimizer.index_coordinator.index_count() == 0);
613 }
614
615 #[test]
616 fn test_advanced_optimizer_optimize_query() {
617 let (row_count, column_count, total_size, column_stats) = create_test_stats();
618 let optimizer = AdvancedQueryOptimizer::new(row_count, column_count, total_size, column_stats);
619 let best_plan = optimizer.optimize_query(5, true);
620 assert!(best_plan.is_some());
621 }
622
623 #[test]
624 fn test_advanced_optimizer_get_top_plans() {
625 let (row_count, column_count, total_size, column_stats) = create_test_stats();
626 let optimizer = AdvancedQueryOptimizer::new(row_count, column_count, total_size, column_stats);
627 let top_plans = optimizer.get_top_plans(5, true, 3);
628 assert!(top_plans.len() > 0);
629 assert!(top_plans.len() <= 3);
630 }
631
632 #[test]
633 fn test_advanced_optimizer_register_index() {
634 let (row_count, column_count, total_size, column_stats) = create_test_stats();
635 let mut optimizer = AdvancedQueryOptimizer::new(row_count, column_count, total_size, column_stats);
636 optimizer.register_index("col_1".to_string(), IndexType::Hash);
637 assert_eq!(optimizer.index_coordinator().index_count(), 1);
638 }
639
640 #[test]
641 fn test_advanced_optimizer_selectivity() {
642 let (row_count, column_count, total_size, column_stats) = create_test_stats();
643 let optimizer = AdvancedQueryOptimizer::new(row_count, column_count, total_size, column_stats);
644 let selectivity = optimizer.get_selectivity("col_0");
645 assert!(selectivity > 0.0);
646 }
647
648 #[test]
649 fn test_cost_comparison_different_strategies() {
650 let (row_count, column_count, total_size, column_stats) = create_test_stats();
651 let estimator = CostEstimator::new(row_count, column_count, total_size, column_stats);
652
653 let full_scan = estimator.estimate_full_scan().total_cost();
654 let pruned = estimator.estimate_column_pruning(5).total_cost();
655 let predicate = estimator.estimate_predicate_pushdown(0.3).total_cost();
656
657 assert!(pruned < full_scan);
658 assert!(predicate < full_scan);
659 }
660
661 #[test]
662 fn test_optimizer_cost_reduction_with_indices() {
663 let (row_count, column_count, total_size, column_stats) = create_test_stats();
664 let estimator = CostEstimator::new(row_count, column_count, total_size, column_stats);
665
666 let baseline = estimator.estimate_full_scan().total_cost();
667 let indexed = estimator.estimate_index_lookup(1000, IndexType::Hash).total_cost();
668
669 assert!(indexed < baseline);
670 }
671
672 #[test]
673 fn test_multi_strategy_optimization() {
674 let (row_count, column_count, total_size, column_stats) = create_test_stats();
675 let cost_estimator = CostEstimator::new(row_count, column_count, total_size, column_stats.clone());
676 let selectivity_estimator = SelectivityEstimator::new(column_stats);
677 let generator = PlanGenerator::new(cost_estimator, selectivity_estimator);
678
679 let plans = generator.generate_plans(8, true);
680
681 let strategies: Vec<_> = plans.iter().map(|p| p.plan.strategy.clone()).collect();
683 assert!(strategies.contains(&ExecutionStrategy::FullTableScan));
684 assert!(strategies.contains(&ExecutionStrategy::CacheHit));
685 }
686
687 #[test]
688 fn test_plan_ranking() {
689 let (row_count, column_count, total_size, column_stats) = create_test_stats();
690 let optimizer = AdvancedQueryOptimizer::new(row_count, column_count, total_size, column_stats);
691 let top_plans = optimizer.get_top_plans(5, true, 5);
692
693 for i in 1..top_plans.len() {
695 assert!(top_plans[i - 1].rank <= top_plans[i].rank);
696 }
697 }
698
699 #[test]
700 fn test_selectivity_product_calculation() {
701 let (_, _, _, column_stats) = create_test_stats();
702 let estimator = SelectivityEstimator::new(column_stats);
703
704 let selectivity1 = estimator.estimate_equality("col_0");
705 let selectivity2 = estimator.estimate_equality("col_1");
706 let combined = estimator.estimate_combined(vec![selectivity1, selectivity2]);
707
708 assert!(combined <= selectivity1);
709 assert!(combined <= selectivity2);
710 }
711
712 #[test]
713 fn test_index_coordinator_defaults() {
714 let coordinator = MultiIndexCoordinator::default();
715 assert_eq!(coordinator.index_count(), 0);
716 }
717
718 #[test]
719 fn test_optimizer_complex_scenario() {
720 let (row_count, column_count, total_size, column_stats) = create_test_stats();
721 let mut optimizer = AdvancedQueryOptimizer::new(row_count, column_count, total_size, column_stats);
722
723 optimizer.register_index("col_0".to_string(), IndexType::Hash);
725 optimizer.register_index("col_1".to_string(), IndexType::BTree);
726 optimizer.register_index("col_2".to_string(), IndexType::Bitmap);
727
728 let best_plan = optimizer.optimize_query(8, true);
730
731 assert!(best_plan.is_some());
732 assert!(best_plan.unwrap().speedup_estimate > 1.0);
733 }
734
735 #[test]
736 fn test_cost_reduction_percentage() {
737 let (row_count, column_count, total_size, column_stats) = create_test_stats();
738 let estimator = CostEstimator::new(row_count, column_count, total_size, column_stats);
739
740 let baseline = estimator.estimate_full_scan();
741 let optimized = estimator.estimate_column_pruning(3);
742
743 let reduction_percent = ((baseline.total_cost() - optimized.total_cost()) / baseline.total_cost()) * 100.0;
744 assert!(reduction_percent > 0.0);
745 }
746
747 #[test]
748 fn test_plan_evaluation_ranking() {
749 let evaluator = PlanEvaluator::new(100.0);
750 let mut plans = vec![
751 CandidatePlan::new(ExecutionPlan::new(ExecutionStrategy::FullTableScan, QueryCost::new(10.0, 1.0, 100.0, 1000)), 100.0),
752 CandidatePlan::new(ExecutionPlan::new(ExecutionStrategy::ColumnPruning, QueryCost::new(5.0, 0.5, 50.0, 1000)), 50.0),
753 CandidatePlan::new(ExecutionPlan::new(ExecutionStrategy::CacheHit, QueryCost::new(0.1, 0.01, 10.0, 1000)), 10.0),
754 ];
755
756 let evaluated = evaluator.evaluate_plans(plans);
757 assert_eq!(evaluated[0].rank, 1);
758 assert!(evaluated[0].speedup_estimate > evaluated[1].speedup_estimate);
759 }
760
761 #[test]
762 fn test_large_scale_optimization() {
763 let row_count = 10_000_000u64;
764 let column_count = 100usize;
765 let total_size = 1_000_000_000.0f64;
766
767 let mut column_stats = HashMap::new();
768 for i in 0..100 {
769 column_stats.insert(
770 format!("col_{}", i),
771 ColumnStatistics {
772 name: format!("col_{}", i),
773 data_type: "Int64".to_string(),
774 row_count,
775 distinct_count: 10000 + (i as u64 * 100),
776 null_count: 10000,
777 min_value: Some("0".to_string()),
778 max_value: Some("9999".to_string()),
779 avg_length: 8.0,
780 compression_ratio: 0.5,
781 },
782 );
783 }
784
785 let optimizer = AdvancedQueryOptimizer::new(row_count, column_count, total_size, column_stats);
786 let best_plan = optimizer.optimize_query(50, true);
787
788 assert!(best_plan.is_some());
789 assert!(best_plan.unwrap().speedup_estimate > 1.0);
790 }
791
792 #[test]
793 fn test_query_cost_cache_reduction() {
794 let cost = QueryCost::new(100.0, 10.0, 1000.0, 1_000_000)
795 .with_cache_probability(0.8);
796 let reduced = cost.with_cache_reduction();
797
798 assert!(reduced.io_cost < cost.io_cost);
799 assert!(reduced.cpu_cost < cost.cpu_cost);
800 }
801
802 #[test]
803 fn test_zero_cost_edge_case() {
804 let evaluator = PlanEvaluator::new(0.1); let plan = ExecutionPlan::new(ExecutionStrategy::CacheHit, QueryCost::new(0.01, 0.001, 1.0, 1000));
806 let candidate = CandidatePlan::new(plan, 0.01);
807
808 let speedup = evaluator.improvement_percentage(&candidate);
809 assert!(speedup >= 0.0 && speedup <= 100.0);
810 }
811}