1use serde::{Deserialize, Serialize};
32use std::collections::HashMap;
33use std::sync::Arc;
34use tokio::sync::RwLock;
35
36#[derive(Debug, Clone)]
38pub struct OptimizationConfig {
39 pub enable_statistics: bool,
41 pub enable_adaptive_learning: bool,
43 pub max_alternative_plans: usize,
45 pub cost_threshold: f64,
47 pub enable_join_optimization: bool,
49 pub enable_index_recommendations: bool,
51 pub statistics_sample_rate: f64,
53}
54
55impl Default for OptimizationConfig {
56 fn default() -> Self {
57 Self {
58 enable_statistics: true,
59 enable_adaptive_learning: true,
60 max_alternative_plans: 5,
61 cost_threshold: 1000.0,
62 enable_join_optimization: true,
63 enable_index_recommendations: true,
64 statistics_sample_rate: 1.0,
65 }
66 }
67}
68
69impl OptimizationConfig {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn with_statistics_collection(mut self, enabled: bool) -> Self {
77 self.enable_statistics = enabled;
78 self
79 }
80
81 pub fn with_adaptive_learning(mut self, enabled: bool) -> Self {
83 self.enable_adaptive_learning = enabled;
84 self
85 }
86
87 pub fn with_max_alternative_plans(mut self, max: usize) -> Self {
89 self.max_alternative_plans = max;
90 self
91 }
92
93 pub fn with_cost_threshold(mut self, threshold: f64) -> Self {
95 self.cost_threshold = threshold;
96 self
97 }
98
99 pub fn with_join_optimization(mut self, enabled: bool) -> Self {
101 self.enable_join_optimization = enabled;
102 self
103 }
104
105 pub fn with_index_recommendations(mut self, enabled: bool) -> Self {
107 self.enable_index_recommendations = enabled;
108 self
109 }
110
111 pub fn with_statistics_sample_rate(mut self, rate: f64) -> Self {
113 self.statistics_sample_rate = rate.clamp(0.0, 1.0);
114 self
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
120pub enum OperationType {
121 FieldSelect,
123 ObjectFetch,
125 ListFetch,
127 Join,
129 Filter,
131 Aggregation,
133 Sort,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct CostFactors {
140 pub cpu_cost: f64,
142 pub io_cost: f64,
144 pub network_cost: f64,
146 pub memory_cost: f64,
148}
149
150impl CostFactors {
151 pub fn new() -> Self {
153 Self {
154 cpu_cost: 0.0,
155 io_cost: 0.0,
156 network_cost: 0.0,
157 memory_cost: 0.0,
158 }
159 }
160
161 pub fn total(&self) -> f64 {
163 self.cpu_cost + self.io_cost + self.network_cost + self.memory_cost
164 }
165
166 pub fn add(&mut self, other: &CostFactors) {
168 self.cpu_cost += other.cpu_cost;
169 self.io_cost += other.io_cost;
170 self.network_cost += other.network_cost;
171 self.memory_cost += other.memory_cost;
172 }
173}
174
175impl Default for CostFactors {
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct OperationCost {
184 pub operation: OperationType,
186 pub name: String,
188 pub factors: CostFactors,
190 pub estimated_rows: usize,
192 pub estimated_time_ms: u64,
194 pub confidence: f64,
196}
197
198impl OperationCost {
199 pub fn new(operation: OperationType, name: String) -> Self {
201 Self {
202 operation,
203 name,
204 factors: CostFactors::new(),
205 estimated_rows: 0,
206 estimated_time_ms: 0,
207 confidence: 0.5,
208 }
209 }
210
211 pub fn with_cpu_cost(mut self, cost: f64) -> Self {
213 self.factors.cpu_cost = cost;
214 self
215 }
216
217 pub fn with_io_cost(mut self, cost: f64) -> Self {
219 self.factors.io_cost = cost;
220 self
221 }
222
223 pub fn with_network_cost(mut self, cost: f64) -> Self {
225 self.factors.network_cost = cost;
226 self
227 }
228
229 pub fn with_memory_cost(mut self, cost: f64) -> Self {
231 self.factors.memory_cost = cost;
232 self
233 }
234
235 pub fn with_estimated_rows(mut self, rows: usize) -> Self {
237 self.estimated_rows = rows;
238 self
239 }
240
241 pub fn with_estimated_time(mut self, time_ms: u64) -> Self {
243 self.estimated_time_ms = time_ms;
244 self
245 }
246
247 pub fn with_confidence(mut self, confidence: f64) -> Self {
249 self.confidence = confidence.clamp(0.0, 1.0);
250 self
251 }
252
253 pub fn total_cost(&self) -> f64 {
255 self.factors.total()
256 }
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct CostBasedPlan {
262 pub plan_id: String,
264 pub operations: Vec<OperationCost>,
266 pub total_cost: f64,
268 pub total_estimated_time_ms: u64,
270 pub strategy: PlanStrategy,
272 pub index_recommendations: Vec<IndexRecommendation>,
274}
275
276impl CostBasedPlan {
277 pub fn new(plan_id: String, strategy: PlanStrategy) -> Self {
279 Self {
280 plan_id,
281 operations: Vec::new(),
282 total_cost: 0.0,
283 total_estimated_time_ms: 0,
284 strategy,
285 index_recommendations: Vec::new(),
286 }
287 }
288
289 pub fn add_operation(&mut self, op: OperationCost) {
291 self.total_cost += op.total_cost();
292 self.total_estimated_time_ms += op.estimated_time_ms;
293 self.operations.push(op);
294 }
295
296 pub fn add_index_recommendation(&mut self, rec: IndexRecommendation) {
298 self.index_recommendations.push(rec);
299 }
300
301 pub fn is_better_than(&self, other: &CostBasedPlan) -> bool {
303 self.total_cost < other.total_cost
304 }
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
309pub enum PlanStrategy {
310 Sequential,
312 Parallel,
314 Batch,
316 Streaming,
318 Hybrid,
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct IndexRecommendation {
325 pub table: String,
327 pub fields: Vec<String>,
329 pub index_type: IndexType,
331 pub expected_improvement: f64,
333 pub rationale: String,
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
339pub enum IndexType {
340 BTree,
342 Hash,
344 FullText,
346 Composite,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct QueryStatistics {
353 pub fingerprint: String,
355 pub execution_count: usize,
357 pub avg_execution_time_ms: f64,
359 pub avg_row_count: f64,
361 pub min_execution_time_ms: u64,
363 pub max_execution_time_ms: u64,
365 pub last_execution: u64,
367}
368
369impl QueryStatistics {
370 pub fn new(fingerprint: String) -> Self {
372 Self {
373 fingerprint,
374 execution_count: 0,
375 avg_execution_time_ms: 0.0,
376 avg_row_count: 0.0,
377 min_execution_time_ms: u64::MAX,
378 max_execution_time_ms: 0,
379 last_execution: 0,
380 }
381 }
382
383 pub fn update(&mut self, execution_time_ms: u64, row_count: usize) {
385 self.execution_count += 1;
386
387 let old_avg = self.avg_execution_time_ms;
389 self.avg_execution_time_ms = (old_avg * (self.execution_count - 1) as f64
390 + execution_time_ms as f64)
391 / self.execution_count as f64;
392
393 let old_row_avg = self.avg_row_count;
395 self.avg_row_count = (old_row_avg * (self.execution_count - 1) as f64 + row_count as f64)
396 / self.execution_count as f64;
397
398 self.min_execution_time_ms = self.min_execution_time_ms.min(execution_time_ms);
400 self.max_execution_time_ms = self.max_execution_time_ms.max(execution_time_ms);
401
402 self.last_execution = chrono::Utc::now().timestamp() as u64;
403 }
404}
405
406pub struct CostModel {
408 weights: HashMap<OperationType, CostFactors>,
410}
411
412impl CostModel {
413 pub fn new() -> Self {
415 let mut weights = HashMap::new();
416
417 let mut field_cost = CostFactors::new();
419 field_cost.cpu_cost = 1.0;
420 field_cost.io_cost = 0.5;
421 weights.insert(OperationType::FieldSelect, field_cost);
422
423 let mut object_cost = CostFactors::new();
424 object_cost.cpu_cost = 2.0;
425 object_cost.io_cost = 5.0;
426 weights.insert(OperationType::ObjectFetch, object_cost);
427
428 let mut list_cost = CostFactors::new();
429 list_cost.cpu_cost = 5.0;
430 list_cost.io_cost = 10.0;
431 weights.insert(OperationType::ListFetch, list_cost);
432
433 let mut join_cost = CostFactors::new();
434 join_cost.cpu_cost = 10.0;
435 join_cost.io_cost = 20.0;
436 weights.insert(OperationType::Join, join_cost);
437
438 let mut filter_cost = CostFactors::new();
439 filter_cost.cpu_cost = 3.0;
440 weights.insert(OperationType::Filter, filter_cost);
441
442 let mut agg_cost = CostFactors::new();
443 agg_cost.cpu_cost = 8.0;
444 agg_cost.memory_cost = 5.0;
445 weights.insert(OperationType::Aggregation, agg_cost);
446
447 let mut sort_cost = CostFactors::new();
448 sort_cost.cpu_cost = 6.0;
449 sort_cost.memory_cost = 4.0;
450 weights.insert(OperationType::Sort, sort_cost);
451
452 Self { weights }
453 }
454
455 pub fn estimate_cost(
457 &mut self,
458 op_type: OperationType,
459 row_count: usize,
460 statistics: Option<&QueryStatistics>,
461 ) -> OperationCost {
462 let base_cost = self.weights.get(&op_type).cloned().unwrap_or_default();
463
464 let mut factors = base_cost.clone();
465
466 let scale_factor = (row_count as f64).ln().max(1.0);
468 factors.cpu_cost *= scale_factor;
469 factors.io_cost *= scale_factor;
470
471 let estimated_time_ms = if let Some(stats) = statistics {
473 (stats.avg_execution_time_ms * (row_count as f64 / stats.avg_row_count)) as u64
474 } else {
475 (row_count as f64 / 100.0).max(1.0) as u64
477 };
478
479 let confidence = if statistics.is_some() { 0.9 } else { 0.5 };
480
481 OperationCost::new(op_type, "operation".to_string())
482 .with_cpu_cost(factors.cpu_cost)
483 .with_io_cost(factors.io_cost)
484 .with_network_cost(factors.network_cost)
485 .with_memory_cost(factors.memory_cost)
486 .with_estimated_rows(row_count)
487 .with_estimated_time(estimated_time_ms)
488 .with_confidence(confidence)
489 }
490
491 pub fn simulate_cost(&mut self, plan: &CostBasedPlan, iterations: usize) -> f64 {
493 let mut total = 0.0;
494
495 for _ in 0..iterations {
496 let mut cost = 0.0;
497 for op in &plan.operations {
498 let variation = 0.9 + fastrand::f64() * 0.2; cost += op.total_cost() * variation;
501 }
502 total += cost;
503 }
504
505 total / iterations as f64
506 }
507}
508
509impl Default for CostModel {
510 fn default() -> Self {
511 Self::new()
512 }
513}
514
515pub struct CostBasedOptimizer {
517 config: OptimizationConfig,
518 cost_model: Arc<RwLock<CostModel>>,
519 statistics: Arc<RwLock<HashMap<String, QueryStatistics>>>,
520}
521
522impl CostBasedOptimizer {
523 pub fn new(config: OptimizationConfig) -> Self {
525 Self {
526 config,
527 cost_model: Arc::new(RwLock::new(CostModel::new())),
528 statistics: Arc::new(RwLock::new(HashMap::new())),
529 }
530 }
531
532 pub async fn optimize(&self, query: &str) -> Result<CostBasedPlan, OptimizationError> {
534 let plans = self.generate_alternative_plans(query).await?;
536
537 let best_plan = self.select_best_plan(plans).await?;
539
540 Ok(best_plan)
541 }
542
543 async fn generate_alternative_plans(
545 &self,
546 query: &str,
547 ) -> Result<Vec<CostBasedPlan>, OptimizationError> {
548 let mut plans = Vec::new();
549
550 let sequential = self.generate_plan(query, PlanStrategy::Sequential).await?;
552 plans.push(sequential);
553
554 if self.config.max_alternative_plans > 1 {
556 let parallel = self.generate_plan(query, PlanStrategy::Parallel).await?;
557 plans.push(parallel);
558 }
559
560 if self.config.max_alternative_plans > 2 {
562 let batch = self.generate_plan(query, PlanStrategy::Batch).await?;
563 plans.push(batch);
564 }
565
566 if self.config.max_alternative_plans > 3 {
568 let streaming = self.generate_plan(query, PlanStrategy::Streaming).await?;
569 plans.push(streaming);
570 }
571
572 if self.config.max_alternative_plans > 4 {
574 let hybrid = self.generate_plan(query, PlanStrategy::Hybrid).await?;
575 plans.push(hybrid);
576 }
577
578 Ok(plans)
579 }
580
581 async fn generate_plan(
583 &self,
584 query: &str,
585 strategy: PlanStrategy,
586 ) -> Result<CostBasedPlan, OptimizationError> {
587 let fingerprint = self.fingerprint_query(query);
588 let mut plan = CostBasedPlan::new(format!("plan_{:?}", strategy), strategy);
589
590 let stats = {
592 let stats_map = self.statistics.read().await;
593 stats_map.get(&fingerprint).cloned()
594 };
595
596 let row_count = 100; let mut cost_model = self.cost_model.write().await;
600
601 let field_op = cost_model.estimate_cost(OperationType::FieldSelect, 10, stats.as_ref());
603 plan.add_operation(field_op);
604
605 let object_op = cost_model.estimate_cost(OperationType::ObjectFetch, 50, stats.as_ref());
606 plan.add_operation(object_op);
607
608 let list_op = cost_model.estimate_cost(OperationType::ListFetch, row_count, stats.as_ref());
609 plan.add_operation(list_op);
610
611 if self.config.enable_index_recommendations {
613 let rec = IndexRecommendation {
614 table: "users".to_string(),
615 fields: vec!["id".to_string()],
616 index_type: IndexType::BTree,
617 expected_improvement: 25.0,
618 rationale: "Frequent lookups on id field".to_string(),
619 };
620 plan.add_index_recommendation(rec);
621 }
622
623 Ok(plan)
624 }
625
626 async fn select_best_plan(
628 &self,
629 plans: Vec<CostBasedPlan>,
630 ) -> Result<CostBasedPlan, OptimizationError> {
631 if plans.is_empty() {
632 return Err(OptimizationError::NoPlanGenerated);
633 }
634
635 let best = plans
637 .into_iter()
638 .min_by(|a, b| {
639 a.total_cost
640 .partial_cmp(&b.total_cost)
641 .expect("cost values should not be NaN")
642 })
643 .expect("plans is not empty (checked above)");
644
645 Ok(best)
646 }
647
648 pub async fn record_execution(
650 &self,
651 query: &str,
652 execution_time_ms: u64,
653 row_count: usize,
654 ) -> Result<(), OptimizationError> {
655 if !self.config.enable_statistics {
656 return Ok(());
657 }
658
659 if fastrand::f64() > self.config.statistics_sample_rate {
661 return Ok(());
662 }
663
664 let fingerprint = self.fingerprint_query(query);
665
666 let mut stats_map = self.statistics.write().await;
667 let stats = stats_map
668 .entry(fingerprint.clone())
669 .or_insert_with(|| QueryStatistics::new(fingerprint));
670
671 stats.update(execution_time_ms, row_count);
672
673 Ok(())
674 }
675
676 pub async fn get_statistics(&self, query: &str) -> Option<QueryStatistics> {
678 let fingerprint = self.fingerprint_query(query);
679 let stats_map = self.statistics.read().await;
680 stats_map.get(&fingerprint).cloned()
681 }
682
683 fn fingerprint_query(&self, query: &str) -> String {
685 let normalized = query.split_whitespace().collect::<Vec<_>>().join(" ");
687
688 let mut hash: u64 = 0xcbf29ce484222325;
690 for byte in normalized.bytes() {
691 hash ^= byte as u64;
692 hash = hash.wrapping_mul(0x100000001b3);
693 }
694
695 format!("{:016x}", hash)
696 }
697
698 pub async fn clear_statistics(&self) {
700 let mut stats_map = self.statistics.write().await;
701 stats_map.clear();
702 }
703
704 pub async fn statistics_count(&self) -> usize {
706 let stats_map = self.statistics.read().await;
707 stats_map.len()
708 }
709}
710
711#[derive(Debug, thiserror::Error)]
713pub enum OptimizationError {
714 #[error("No execution plan could be generated")]
716 NoPlanGenerated,
717
718 #[error("Cost estimation failed: {0}")]
720 CostEstimationFailed(String),
721
722 #[error("Statistics error: {0}")]
724 StatisticsError(String),
725
726 #[error("Invalid query: {0}")]
728 InvalidQuery(String),
729}
730
731#[cfg(test)]
732mod tests {
733 use super::*;
734
735 #[test]
736 fn test_optimization_config_builder() {
737 let config = OptimizationConfig::new()
738 .with_statistics_collection(true)
739 .with_adaptive_learning(true)
740 .with_max_alternative_plans(10)
741 .with_cost_threshold(500.0)
742 .with_join_optimization(true)
743 .with_index_recommendations(true)
744 .with_statistics_sample_rate(0.5);
745
746 assert!(config.enable_statistics);
747 assert!(config.enable_adaptive_learning);
748 assert_eq!(config.max_alternative_plans, 10);
749 assert_eq!(config.cost_threshold, 500.0);
750 assert!(config.enable_join_optimization);
751 assert!(config.enable_index_recommendations);
752 assert_eq!(config.statistics_sample_rate, 0.5);
753 }
754
755 #[test]
756 fn test_cost_factors_total() {
757 let mut factors = CostFactors::new();
758 factors.cpu_cost = 10.0;
759 factors.io_cost = 5.0;
760 factors.network_cost = 3.0;
761 factors.memory_cost = 2.0;
762
763 assert_eq!(factors.total(), 20.0);
764 }
765
766 #[test]
767 fn test_cost_factors_add() {
768 let mut factors1 = CostFactors::new();
769 factors1.cpu_cost = 10.0;
770 factors1.io_cost = 5.0;
771
772 let mut factors2 = CostFactors::new();
773 factors2.cpu_cost = 3.0;
774 factors2.network_cost = 2.0;
775
776 factors1.add(&factors2);
777
778 assert_eq!(factors1.cpu_cost, 13.0);
779 assert_eq!(factors1.io_cost, 5.0);
780 assert_eq!(factors1.network_cost, 2.0);
781 }
782
783 #[test]
784 fn test_operation_cost_creation() {
785 let cost = OperationCost::new(OperationType::FieldSelect, "test".to_string())
786 .with_cpu_cost(5.0)
787 .with_io_cost(2.0)
788 .with_estimated_rows(100)
789 .with_estimated_time(50)
790 .with_confidence(0.8);
791
792 assert_eq!(cost.operation, OperationType::FieldSelect);
793 assert_eq!(cost.factors.cpu_cost, 5.0);
794 assert_eq!(cost.factors.io_cost, 2.0);
795 assert_eq!(cost.estimated_rows, 100);
796 assert_eq!(cost.estimated_time_ms, 50);
797 assert_eq!(cost.confidence, 0.8);
798 }
799
800 #[test]
801 fn test_operation_cost_total() {
802 let cost = OperationCost::new(OperationType::Join, "join".to_string())
803 .with_cpu_cost(10.0)
804 .with_io_cost(20.0)
805 .with_network_cost(5.0);
806
807 assert_eq!(cost.total_cost(), 35.0);
808 }
809
810 #[test]
811 fn test_plan_creation() {
812 let plan = CostBasedPlan::new("plan1".to_string(), PlanStrategy::Sequential);
813
814 assert_eq!(plan.plan_id, "plan1");
815 assert_eq!(plan.strategy, PlanStrategy::Sequential);
816 assert_eq!(plan.total_cost, 0.0);
817 assert_eq!(plan.total_estimated_time_ms, 0);
818 }
819
820 #[test]
821 fn test_plan_add_operation() {
822 let mut plan = CostBasedPlan::new("plan1".to_string(), PlanStrategy::Parallel);
823
824 let op1 = OperationCost::new(OperationType::FieldSelect, "field1".to_string())
825 .with_cpu_cost(5.0)
826 .with_estimated_time(10);
827
828 let op2 = OperationCost::new(OperationType::ObjectFetch, "obj1".to_string())
829 .with_cpu_cost(10.0)
830 .with_estimated_time(20);
831
832 plan.add_operation(op1);
833 plan.add_operation(op2);
834
835 assert_eq!(plan.operations.len(), 2);
836 assert_eq!(plan.total_cost, 15.0);
837 assert_eq!(plan.total_estimated_time_ms, 30);
838 }
839
840 #[test]
841 fn test_plan_comparison() {
842 let mut plan1 = CostBasedPlan::new("plan1".to_string(), PlanStrategy::Sequential);
843 plan1.total_cost = 100.0;
844
845 let mut plan2 = CostBasedPlan::new("plan2".to_string(), PlanStrategy::Parallel);
846 plan2.total_cost = 50.0;
847
848 assert!(plan2.is_better_than(&plan1));
849 assert!(!plan1.is_better_than(&plan2));
850 }
851
852 #[test]
853 fn test_query_statistics_creation() {
854 let stats = QueryStatistics::new("fingerprint123".to_string());
855
856 assert_eq!(stats.fingerprint, "fingerprint123");
857 assert_eq!(stats.execution_count, 0);
858 assert_eq!(stats.avg_execution_time_ms, 0.0);
859 }
860
861 #[test]
862 fn test_query_statistics_update() {
863 let mut stats = QueryStatistics::new("test".to_string());
864
865 stats.update(100, 50);
866 assert_eq!(stats.execution_count, 1);
867 assert_eq!(stats.avg_execution_time_ms, 100.0);
868 assert_eq!(stats.avg_row_count, 50.0);
869 assert_eq!(stats.min_execution_time_ms, 100);
870 assert_eq!(stats.max_execution_time_ms, 100);
871
872 stats.update(200, 100);
873 assert_eq!(stats.execution_count, 2);
874 assert_eq!(stats.avg_execution_time_ms, 150.0);
875 assert_eq!(stats.avg_row_count, 75.0);
876 assert_eq!(stats.min_execution_time_ms, 100);
877 assert_eq!(stats.max_execution_time_ms, 200);
878 }
879
880 #[test]
881 fn test_cost_model_creation() {
882 let model = CostModel::new();
883 assert!(!model.weights.is_empty());
884 }
885
886 #[test]
887 fn test_cost_model_estimate() {
888 let mut model = CostModel::new();
889
890 let cost = model.estimate_cost(OperationType::FieldSelect, 100, None);
891
892 assert_eq!(cost.operation, OperationType::FieldSelect);
893 assert!(cost.total_cost() > 0.0);
894 assert_eq!(cost.estimated_rows, 100);
895 assert_eq!(cost.confidence, 0.5);
896 }
897
898 #[test]
899 fn test_cost_model_with_statistics() {
900 let mut model = CostModel::new();
901 let mut stats = QueryStatistics::new("test".to_string());
902 stats.update(100, 50);
903 stats.update(200, 100);
904
905 let cost = model.estimate_cost(OperationType::ObjectFetch, 100, Some(&stats));
906
907 assert!(cost.confidence > 0.5);
908 assert!(cost.estimated_time_ms > 0);
909 }
910
911 #[test]
912 fn test_monte_carlo_simulation() {
913 let mut model = CostModel::new();
914 let mut plan = CostBasedPlan::new("test".to_string(), PlanStrategy::Sequential);
915
916 let op = OperationCost::new(OperationType::Join, "join".to_string()).with_cpu_cost(50.0);
917
918 plan.add_operation(op);
919
920 let simulated_cost = model.simulate_cost(&plan, 100);
921 assert!(simulated_cost > 0.0);
922 }
923
924 #[tokio::test]
925 async fn test_optimizer_creation() {
926 let config = OptimizationConfig::new();
927 let optimizer = CostBasedOptimizer::new(config);
928
929 assert_eq!(optimizer.statistics_count().await, 0);
930 }
931
932 #[tokio::test]
933 async fn test_query_optimization() {
934 let config = OptimizationConfig::new();
935 let optimizer = CostBasedOptimizer::new(config);
936
937 let query = "{ user { id name posts { title } } }";
938 let plan = optimizer.optimize(query).await;
939
940 assert!(plan.is_ok());
941 let plan = plan.expect("should succeed");
942 assert!(!plan.operations.is_empty());
943 assert!(plan.total_cost > 0.0);
944 }
945
946 #[tokio::test]
947 async fn test_alternative_plans_generation() {
948 let config = OptimizationConfig::new().with_max_alternative_plans(5);
949 let optimizer = CostBasedOptimizer::new(config);
950
951 let query = "{ test }";
952 let plans = optimizer
953 .generate_alternative_plans(query)
954 .await
955 .expect("should succeed");
956
957 assert_eq!(plans.len(), 5);
958 assert!(plans.iter().any(|p| p.strategy == PlanStrategy::Sequential));
959 assert!(plans.iter().any(|p| p.strategy == PlanStrategy::Parallel));
960 assert!(plans.iter().any(|p| p.strategy == PlanStrategy::Batch));
961 assert!(plans.iter().any(|p| p.strategy == PlanStrategy::Streaming));
962 assert!(plans.iter().any(|p| p.strategy == PlanStrategy::Hybrid));
963 }
964
965 #[tokio::test]
966 async fn test_best_plan_selection() {
967 let config = OptimizationConfig::new();
968 let optimizer = CostBasedOptimizer::new(config);
969
970 let mut plan1 = CostBasedPlan::new("plan1".to_string(), PlanStrategy::Sequential);
971 plan1.total_cost = 100.0;
972
973 let mut plan2 = CostBasedPlan::new("plan2".to_string(), PlanStrategy::Parallel);
974 plan2.total_cost = 50.0;
975
976 let mut plan3 = CostBasedPlan::new("plan3".to_string(), PlanStrategy::Batch);
977 plan3.total_cost = 75.0;
978
979 let plans = vec![plan1, plan2.clone(), plan3];
980 let best = optimizer
981 .select_best_plan(plans)
982 .await
983 .expect("should succeed");
984
985 assert_eq!(best.plan_id, plan2.plan_id);
986 assert_eq!(best.total_cost, 50.0);
987 }
988
989 #[tokio::test]
990 async fn test_record_execution() {
991 let config = OptimizationConfig::new().with_statistics_collection(true);
992 let optimizer = CostBasedOptimizer::new(config);
993
994 let query = "{ test }";
995 optimizer
996 .record_execution(query, 100, 50)
997 .await
998 .expect("should succeed");
999
1000 let stats = optimizer.get_statistics(query).await;
1001 assert!(stats.is_some());
1002
1003 let stats = stats.expect("should succeed");
1004 assert_eq!(stats.execution_count, 1);
1005 assert_eq!(stats.avg_execution_time_ms, 100.0);
1006 }
1007
1008 #[tokio::test]
1009 async fn test_statistics_disabled() {
1010 let config = OptimizationConfig::new().with_statistics_collection(false);
1011 let optimizer = CostBasedOptimizer::new(config);
1012
1013 let query = "{ test }";
1014 optimizer
1015 .record_execution(query, 100, 50)
1016 .await
1017 .expect("should succeed");
1018
1019 assert_eq!(optimizer.statistics_count().await, 0);
1020 }
1021
1022 #[tokio::test]
1023 async fn test_clear_statistics() {
1024 let config = OptimizationConfig::new();
1025 let optimizer = CostBasedOptimizer::new(config);
1026
1027 optimizer
1028 .record_execution("{ test1 }", 100, 50)
1029 .await
1030 .expect("should succeed");
1031 optimizer
1032 .record_execution("{ test2 }", 200, 100)
1033 .await
1034 .expect("should succeed");
1035
1036 assert_eq!(optimizer.statistics_count().await, 2);
1037
1038 optimizer.clear_statistics().await;
1039 assert_eq!(optimizer.statistics_count().await, 0);
1040 }
1041
1042 #[tokio::test]
1043 async fn test_query_fingerprinting() {
1044 let config = OptimizationConfig::new();
1045 let optimizer = CostBasedOptimizer::new(config);
1046
1047 let query1 = "{ user { id name } }";
1048 let query2 = "{ user { id name } }"; let fp1 = optimizer.fingerprint_query(query1);
1051 let fp2 = optimizer.fingerprint_query(query2);
1052
1053 assert_eq!(fp1, fp2);
1054 }
1055
1056 #[tokio::test]
1057 async fn test_index_recommendations() {
1058 let config = OptimizationConfig::new().with_index_recommendations(true);
1059 let optimizer = CostBasedOptimizer::new(config);
1060
1061 let query = "{ users { id } }";
1062 let plan = optimizer.optimize(query).await.expect("should succeed");
1063
1064 assert!(!plan.index_recommendations.is_empty());
1065 assert!(plan.index_recommendations[0].expected_improvement > 0.0);
1066 }
1067
1068 #[test]
1069 fn test_index_recommendation_creation() {
1070 let rec = IndexRecommendation {
1071 table: "users".to_string(),
1072 fields: vec!["email".to_string()],
1073 index_type: IndexType::Hash,
1074 expected_improvement: 30.0,
1075 rationale: "Frequent equality lookups".to_string(),
1076 };
1077
1078 assert_eq!(rec.table, "users");
1079 assert_eq!(rec.fields.len(), 1);
1080 assert_eq!(rec.index_type, IndexType::Hash);
1081 assert_eq!(rec.expected_improvement, 30.0);
1082 }
1083
1084 #[test]
1085 fn test_confidence_clamping() {
1086 let cost =
1087 OperationCost::new(OperationType::FieldSelect, "test".to_string()).with_confidence(1.5);
1088
1089 assert_eq!(cost.confidence, 1.0);
1090
1091 let cost2 = OperationCost::new(OperationType::FieldSelect, "test".to_string())
1092 .with_confidence(-0.5);
1093
1094 assert_eq!(cost2.confidence, 0.0);
1095 }
1096}