Skip to main content

kore_fileformat/
query_optimizer.rs

1use crate::query_execution::{ExecutionPlan, ExecutionStrategy, QueryCost};
2use crate::statistics::ColumnStatistics;
3use crate::indexing::{IndexType, IndexSelector};
4use std::collections::HashMap;
5
6/// Cost estimator for different query execution strategies
7#[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    /// Create new cost estimator
17    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    /// Estimate cost for full table scan
32    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    /// Estimate cost for predicate pushdown
41    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; // Higher CPU for filtering
45        let memory_cost = (filtered_rows as f64) / 100.0;
46        
47        QueryCost::new(io_cost, cpu_cost, memory_cost, filtered_rows)
48    }
49
50    /// Estimate cost for column pruning
51    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    /// Estimate cost for index-based lookup
61    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; // Very low CPU for index lookup
67        let memory_cost = 100.0; // Index memory is minimal
68        
69        QueryCost::new(io_cost, cpu_cost, memory_cost, cardinality as u64)
70    }
71
72    /// Estimate cost for cached query
73    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/// Candidate execution plan with ranking
80#[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    /// Create new candidate
90    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    /// Set speedup estimate relative to baseline
100    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    /// Set plan ranking
106    pub fn with_rank(mut self, rank: usize) -> Self {
107        self.rank = rank;
108        self
109    }
110}
111
112/// Selectivity estimator using statistics
113#[derive(Debug, Clone)]
114pub struct SelectivityEstimator {
115    column_stats: HashMap<String, ColumnStatistics>,
116}
117
118impl SelectivityEstimator {
119    /// Create new selectivity estimator
120    pub fn new(column_stats: HashMap<String, ColumnStatistics>) -> Self {
121        Self { column_stats }
122    }
123
124    /// Estimate selectivity for equality predicate
125    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 // Default assumption
130        }
131    }
132
133    /// Estimate selectivity for range predicate
134    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 // Default: 50% of rows
139        }
140    }
141
142    /// Estimate selectivity for IN predicate
143    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    /// Estimate combined selectivity for multiple predicates (AND logic)
153    pub fn estimate_combined(&self, selectivities: Vec<f64>) -> f64 {
154        selectivities.iter().product()
155    }
156}
157
158/// Plan generator for creating candidate execution plans
159#[derive(Debug, Clone)]
160pub struct PlanGenerator {
161    cost_estimator: CostEstimator,
162    selectivity_estimator: SelectivityEstimator,
163}
164
165impl PlanGenerator {
166    /// Create new plan generator
167    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    /// Generate candidate plans
178    pub fn generate_plans(&self, num_columns: usize, has_predicates: bool) -> Vec<CandidatePlan> {
179        let mut plans = Vec::new();
180
181        // Plan 1: Full table scan
182        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        // Plan 2: Column pruning (if applicable)
194        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        // Plan 3: Predicate pushdown (if applicable)
209        if has_predicates {
210            let selectivity = 0.3; // Assume 30% selectivity
211            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        // Plan 4: Combined optimization
225        if num_columns > 0 && has_predicates {
226            let selectivity = 0.3;
227            let combined_io = (baseline_total * 0.3 * 0.5) / 20.0; // 70% reduction
228            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        // Plan 5: Cached query
248        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        // Sort by cost (ascending)
261        plans.sort_by(|a, b| a.cost.partial_cmp(&b.cost).unwrap_or(std::cmp::Ordering::Equal));
262
263        plans
264    }
265}
266
267/// Multi-index coordinator
268#[derive(Debug, Clone)]
269pub struct MultiIndexCoordinator {
270    available_indices: HashMap<String, IndexType>,
271}
272
273impl MultiIndexCoordinator {
274    /// Create new coordinator
275    pub fn new() -> Self {
276        Self {
277            available_indices: HashMap::new(),
278        }
279    }
280
281    /// Register available index
282    pub fn register_index(&mut self, column: String, index_type: IndexType) {
283        self.available_indices.insert(column, index_type);
284    }
285
286    /// Find best index for column
287    pub fn find_best_index(&self, column: &str) -> Option<IndexType> {
288        self.available_indices.get(column).cloned()
289    }
290
291    /// Get all registered columns
292    pub fn registered_columns(&self) -> Vec<String> {
293        self.available_indices.keys().cloned().collect()
294    }
295
296    /// Get index count
297    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/// Plan evaluator for comparing and ranking plans
309#[derive(Debug, Clone)]
310pub struct PlanEvaluator {
311    baseline_cost: f64,
312}
313
314impl PlanEvaluator {
315    /// Create new evaluator
316    pub fn new(baseline_cost: f64) -> Self {
317        Self { baseline_cost }
318    }
319
320    /// Evaluate and rank plans
321    pub fn evaluate_plans(&self, mut plans: Vec<CandidatePlan>) -> Vec<CandidatePlan> {
322        // Calculate speedup for each plan
323        for plan in &mut plans {
324            plan.speedup_estimate = self.baseline_cost / plan.cost.max(0.01);
325        }
326
327        // Sort by speedup (descending)
328        plans.sort_by(|a, b| {
329            b.speedup_estimate.partial_cmp(&a.speedup_estimate)
330                .unwrap_or(std::cmp::Ordering::Equal)
331        });
332
333        // Assign final ranks
334        for (idx, plan) in plans.iter_mut().enumerate() {
335            plan.rank = idx + 1;
336        }
337
338        plans
339    }
340
341    /// Get best plan (lowest cost)
342    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    /// Calculate improvement percentage
349    pub fn improvement_percentage(&self, plan: &CandidatePlan) -> f64 {
350        ((self.baseline_cost - plan.cost) / self.baseline_cost) * 100.0
351    }
352}
353
354/// Advanced query optimizer
355#[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    /// Create new optimizer
366    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    /// Optimize query and return best plan
389    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    /// Get top N plans
396    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    /// Register index for optimization
403    pub fn register_index(&mut self, column: String, index_type: IndexType) {
404        self.index_coordinator.register_index(column, index_type);
405    }
406
407    /// Get index coordinator
408    pub fn index_coordinator(&self) -> &MultiIndexCoordinator {
409        &self.index_coordinator
410    }
411
412    /// Get selectivity for column
413    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; // 100 MB
426
427        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        // Should generate multiple strategies
682        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        // Plans should be ranked from best to worst
694        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        // Register multiple indices
724        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        // Get optimization recommendation
729        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); // Very small baseline
805        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}