1use anyhow::Result;
17use scirs2_core::metrics::{Counter, Gauge};
18use serde::{Deserialize, Serialize};
19use std::collections::{HashMap, VecDeque};
20use std::sync::Arc;
21use std::time::{Duration, Instant};
22use sysinfo::System;
23use tokio::sync::RwLock;
24
25use crate::historical_cost_estimator::HistoricalCostEstimator;
26use crate::ml_optimizer::{MLOptimizerConfig, MLQueryOptimizer, QueryFeatures, TrainingSample};
27use crate::performance::PerformanceTracker;
28
29#[derive(Debug, Clone)]
31pub struct DynamicPlannerConfig {
32 pub enabled: bool,
34 pub min_adaptation_threshold_ms: f64,
36 pub cpu_threshold: f64,
38 pub memory_threshold: f64,
40 pub enable_ml_prediction: bool,
42 pub history_size: usize,
44 pub adaptation_interval: Duration,
46 pub aggressive_mode_enabled: bool,
48}
49
50impl Default for DynamicPlannerConfig {
51 fn default() -> Self {
52 Self {
53 enabled: true,
54 min_adaptation_threshold_ms: 100.0,
55 cpu_threshold: 0.80, memory_threshold: 0.85, enable_ml_prediction: true,
58 history_size: 100,
59 adaptation_interval: Duration::from_secs(10),
60 aggressive_mode_enabled: true,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
67pub enum ExecutionStrategy {
68 Sequential,
70 Parallel,
72 Batched,
74 Streaming,
76 Cached,
78 Optimized,
80 Degraded,
82}
83
84impl ExecutionStrategy {
85 pub fn overhead_multiplier(&self) -> f64 {
87 match self {
88 Self::Sequential => 1.0,
89 Self::Parallel => 1.2, Self::Batched => 0.8, Self::Streaming => 1.1, Self::Cached => 0.01, Self::Optimized => 0.7, Self::Degraded => 1.5, }
96 }
97
98 pub fn is_high_load_safe(&self) -> bool {
100 matches!(self, Self::Degraded | Self::Cached | Self::Sequential)
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct ResourceSnapshot {
107 #[serde(skip, default = "Instant::now")]
108 pub timestamp: Instant,
109 pub cpu_usage: f64,
110 pub memory_usage: f64,
111 pub active_queries: usize,
112 pub avg_query_time_ms: f64,
113}
114
115impl ResourceSnapshot {
116 pub fn is_high_load(&self, config: &DynamicPlannerConfig) -> bool {
118 self.cpu_usage > config.cpu_threshold || self.memory_usage > config.memory_threshold
119 }
120
121 pub fn load_severity(&self, config: &DynamicPlannerConfig) -> f64 {
123 let cpu_severity = (self.cpu_usage / config.cpu_threshold).min(1.0);
124 let memory_severity = (self.memory_usage / config.memory_threshold).min(1.0);
125
126 cpu_severity.max(memory_severity)
127 }
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct AdaptivePlan {
133 pub query_fingerprint: String,
134 pub strategy: ExecutionStrategy,
135 pub estimated_cost: f64,
136 pub estimated_time_ms: f64,
137 pub confidence: f64,
138 pub fallback_strategy: Option<ExecutionStrategy>,
139 #[serde(skip, default = "Instant::now")]
140 pub created_at: Instant,
141 pub metadata: HashMap<String, String>,
142}
143
144impl AdaptivePlan {
145 pub fn new(
146 query_fingerprint: String,
147 strategy: ExecutionStrategy,
148 estimated_cost: f64,
149 ) -> Self {
150 Self {
151 query_fingerprint,
152 strategy,
153 estimated_cost,
154 estimated_time_ms: estimated_cost * 10.0, confidence: 0.5,
156 fallback_strategy: None,
157 created_at: Instant::now(),
158 metadata: HashMap::new(),
159 }
160 }
161
162 pub fn with_fallback(mut self, fallback: ExecutionStrategy) -> Self {
163 self.fallback_strategy = Some(fallback);
164 self
165 }
166
167 pub fn with_confidence(mut self, confidence: f64) -> Self {
168 self.confidence = confidence.clamp(0.0, 1.0);
169 self
170 }
171
172 pub fn with_estimated_time(mut self, time_ms: f64) -> Self {
173 self.estimated_time_ms = time_ms;
174 self
175 }
176}
177
178#[derive(Debug, Clone)]
180pub struct ExecutionResult {
181 pub query_fingerprint: String,
182 pub strategy_used: ExecutionStrategy,
183 pub actual_time_ms: f64,
184 pub success: bool,
185 pub error_message: Option<String>,
186 pub resource_snapshot: ResourceSnapshot,
187}
188
189pub struct DynamicQueryPlanner {
191 config: DynamicPlannerConfig,
192 cost_estimator: Arc<RwLock<HistoricalCostEstimator>>,
193 ml_optimizer: Option<Arc<RwLock<MLQueryOptimizer>>>,
194 execution_history: Arc<RwLock<VecDeque<ExecutionResult>>>,
195 strategy_performance: Arc<RwLock<HashMap<ExecutionStrategy, StrategyStats>>>,
196 system: Arc<RwLock<System>>,
197
198 adaptations: Arc<Counter>,
200 strategy_switches: Arc<Counter>,
201 high_load_events: Arc<Counter>,
202 avg_adaptation_quality: Arc<Gauge>,
203}
204
205#[derive(Debug, Clone)]
207pub struct StrategyStats {
208 pub total_executions: u64,
209 pub successful_executions: u64,
210 pub total_time_ms: f64,
211 pub avg_time_ms: f64,
212 #[allow(dead_code)]
213 pub p95_time_ms: f64,
214 pub error_rate: f64,
215 #[allow(dead_code)]
216 last_used: Instant,
217}
218
219impl Default for StrategyStats {
220 fn default() -> Self {
221 Self {
222 total_executions: 0,
223 successful_executions: 0,
224 total_time_ms: 0.0,
225 avg_time_ms: 0.0,
226 p95_time_ms: 0.0,
227 error_rate: 0.0,
228 last_used: Instant::now(),
229 }
230 }
231}
232
233impl DynamicQueryPlanner {
234 pub fn new(config: DynamicPlannerConfig) -> Self {
235 let ml_optimizer = if config.enable_ml_prediction {
236 let tracker = Arc::new(PerformanceTracker::new());
237 let optimizer = MLQueryOptimizer::new(MLOptimizerConfig::default(), tracker);
238 Some(Arc::new(RwLock::new(optimizer)))
239 } else {
240 None
241 };
242
243 Self {
244 cost_estimator: Arc::new(RwLock::new(HistoricalCostEstimator::new())),
245 ml_optimizer,
246 execution_history: Arc::new(RwLock::new(VecDeque::with_capacity(config.history_size))),
247 strategy_performance: Arc::new(RwLock::new(HashMap::new())),
248 system: Arc::new(RwLock::new(System::new_all())),
249 adaptations: Arc::new(Counter::new("dynamic_planner_adaptations".to_string())),
250 strategy_switches: Arc::new(Counter::new(
251 "dynamic_planner_strategy_switches".to_string(),
252 )),
253 high_load_events: Arc::new(Counter::new(
254 "dynamic_planner_high_load_events".to_string(),
255 )),
256 avg_adaptation_quality: Arc::new(Gauge::new(
257 "dynamic_planner_adaptation_quality".to_string(),
258 )),
259 config,
260 }
261 }
262
263 pub async fn create_plan(&self, query: &str, query_complexity: f64) -> Result<AdaptivePlan> {
265 if !self.config.enabled {
266 return Ok(AdaptivePlan::new(
268 Self::fingerprint_query(query),
269 ExecutionStrategy::Sequential,
270 query_complexity,
271 ));
272 }
273
274 let query_fingerprint = Self::fingerprint_query(query);
275
276 let snapshot = self.get_resource_snapshot().await?;
278
279 let is_high_load = snapshot.is_high_load(&self.config);
281 if is_high_load {
282 self.high_load_events.inc();
283 }
284
285 let cost_estimate = {
287 let estimator = self.cost_estimator.read().await;
288 estimator.estimate_cost(&query_fingerprint).ok()
289 };
290
291 let strategy = self
293 .select_optimal_strategy(
294 &query_fingerprint,
295 query_complexity,
296 &snapshot,
297 cost_estimate.as_ref(),
298 )
299 .await?;
300
301 let fallback = self.select_fallback_strategy(strategy, &snapshot);
303
304 let mut plan = AdaptivePlan::new(query_fingerprint.clone(), strategy, query_complexity)
306 .with_fallback(fallback);
307
308 if let Some(estimate) = cost_estimate {
310 plan = plan
311 .with_estimated_time(estimate.estimated_time_ms)
312 .with_confidence(estimate.confidence);
313 }
314
315 self.adaptations.inc();
316
317 Ok(plan)
318 }
319
320 async fn select_optimal_strategy(
322 &self,
323 query_fingerprint: &str,
324 complexity: f64,
325 snapshot: &ResourceSnapshot,
326 cost_estimate: Option<&crate::historical_cost_estimator::CostEstimate>,
327 ) -> Result<ExecutionStrategy> {
328 if snapshot.is_high_load(&self.config) {
330 return Ok(self.select_high_load_strategy(snapshot).await);
331 }
332
333 if let Some(ml_opt) = &self.ml_optimizer {
335 let ml = ml_opt.read().await;
336 if let Ok(strategy) = self
337 .ml_predict_strategy(&ml, query_fingerprint, complexity, snapshot)
338 .await
339 {
340 return Ok(strategy);
341 }
342 }
343
344 if let Some(estimate) = cost_estimate {
346 return Ok(self
347 .select_strategy_from_history(estimate, complexity, snapshot)
348 .await);
349 }
350
351 Ok(self.select_default_strategy(complexity, snapshot))
353 }
354
355 async fn ml_predict_strategy(
357 &self,
358 _ml: &MLQueryOptimizer,
359 _query_fingerprint: &str,
360 complexity: f64,
361 snapshot: &ResourceSnapshot,
362 ) -> Result<ExecutionStrategy> {
363 let _features = [
365 complexity,
366 snapshot.cpu_usage,
367 snapshot.memory_usage,
368 snapshot.active_queries as f64,
369 snapshot.avg_query_time_ms,
370 ];
371
372 let stats = self.strategy_performance.read().await;
374
375 let mut best_strategy = ExecutionStrategy::Sequential;
377 let mut best_score = f64::MIN;
378
379 for strategy in &[
380 ExecutionStrategy::Sequential,
381 ExecutionStrategy::Parallel,
382 ExecutionStrategy::Batched,
383 ExecutionStrategy::Streaming,
384 ExecutionStrategy::Optimized,
385 ] {
386 let score = if let Some(stat) = stats.get(strategy) {
387 let success_rate =
389 stat.successful_executions as f64 / stat.total_executions.max(1) as f64;
390 let time_penalty = stat.avg_time_ms / 1000.0; (success_rate * 10.0) - time_penalty
392 } else {
393 match strategy {
395 ExecutionStrategy::Optimized => 8.0,
396 ExecutionStrategy::Parallel => 7.0,
397 ExecutionStrategy::Batched => 6.0,
398 ExecutionStrategy::Streaming => 5.0,
399 ExecutionStrategy::Sequential => 4.0,
400 ExecutionStrategy::Cached => 10.0,
401 ExecutionStrategy::Degraded => 2.0,
402 }
403 };
404
405 if score > best_score {
406 best_score = score;
407 best_strategy = *strategy;
408 }
409 }
410
411 Ok(best_strategy)
412 }
413
414 async fn select_strategy_from_history(
416 &self,
417 estimate: &crate::historical_cost_estimator::CostEstimate,
418 complexity: f64,
419 snapshot: &ResourceSnapshot,
420 ) -> ExecutionStrategy {
421 if estimate.estimated_time_ms < self.config.min_adaptation_threshold_ms {
423 return ExecutionStrategy::Sequential;
424 }
425
426 if complexity > 50.0 && !snapshot.is_high_load(&self.config) {
428 return ExecutionStrategy::Parallel;
429 }
430
431 if estimate.confidence < 0.5 {
433 return ExecutionStrategy::Optimized;
434 }
435
436 if complexity > 20.0 {
438 ExecutionStrategy::Optimized
439 } else {
440 ExecutionStrategy::Sequential
441 }
442 }
443
444 fn select_default_strategy(
446 &self,
447 complexity: f64,
448 snapshot: &ResourceSnapshot,
449 ) -> ExecutionStrategy {
450 if snapshot.is_high_load(&self.config) {
451 return ExecutionStrategy::Degraded;
452 }
453
454 match complexity {
455 c if c < 10.0 => ExecutionStrategy::Sequential,
456 c if c < 30.0 => ExecutionStrategy::Batched,
457 c if c < 60.0 => ExecutionStrategy::Optimized,
458 _ => ExecutionStrategy::Parallel,
459 }
460 }
461
462 async fn select_high_load_strategy(&self, snapshot: &ResourceSnapshot) -> ExecutionStrategy {
464 let severity = snapshot.load_severity(&self.config);
465
466 if severity > 0.95 {
467 ExecutionStrategy::Degraded
469 } else if severity > 0.85 {
470 ExecutionStrategy::Sequential
472 } else {
473 ExecutionStrategy::Batched
475 }
476 }
477
478 fn select_fallback_strategy(
480 &self,
481 primary: ExecutionStrategy,
482 snapshot: &ResourceSnapshot,
483 ) -> ExecutionStrategy {
484 if snapshot.is_high_load(&self.config) {
485 return ExecutionStrategy::Degraded;
486 }
487
488 match primary {
489 ExecutionStrategy::Parallel => ExecutionStrategy::Sequential,
490 ExecutionStrategy::Optimized => ExecutionStrategy::Batched,
491 ExecutionStrategy::Batched => ExecutionStrategy::Sequential,
492 ExecutionStrategy::Streaming => ExecutionStrategy::Sequential,
493 _ => ExecutionStrategy::Sequential,
494 }
495 }
496
497 pub async fn record_execution(&self, result: ExecutionResult) -> Result<()> {
499 let query_fingerprint = result.query_fingerprint.clone();
500 let strategy = result.strategy_used;
501 let execution_time = result.actual_time_ms;
502 let success = result.success;
503 let resource_snapshot = result.resource_snapshot.clone();
504
505 {
507 let mut estimator = self.cost_estimator.write().await;
508 let _ = estimator.record_execution(
509 &query_fingerprint,
510 execution_time,
511 1, 1024, );
514 }
515
516 {
518 let mut stats = self.strategy_performance.write().await;
519 let strategy_stat = stats.entry(strategy).or_default();
520
521 strategy_stat.total_executions += 1;
522 if success {
523 strategy_stat.successful_executions += 1;
524 }
525 strategy_stat.total_time_ms += execution_time;
526 strategy_stat.avg_time_ms =
527 strategy_stat.total_time_ms / strategy_stat.total_executions as f64;
528 strategy_stat.error_rate = 1.0
529 - (strategy_stat.successful_executions as f64
530 / strategy_stat.total_executions as f64);
531 strategy_stat.last_used = Instant::now();
532 }
533
534 {
536 let mut history = self.execution_history.write().await;
537 if history.len() >= self.config.history_size {
538 history.pop_front();
539 }
540 history.push_back(result);
541 }
542
543 if let Some(ml_opt) = &self.ml_optimizer {
545 let features = QueryFeatures {
546 field_count: resource_snapshot.active_queries as f64,
547 max_depth: 1.0,
548 complexity_score: execution_time,
549 selection_count: resource_snapshot.active_queries as f64,
550 has_fragments: 0.0,
551 has_variables: 0.0,
552 operation_type: 0.0,
553 unique_field_types: 1.0,
554 nested_list_count: 0.0,
555 argument_count: 0.0,
556 directive_count: 0.0,
557 estimated_result_size: resource_snapshot.avg_query_time_ms.log10().max(1.0),
558 };
559 let sample = TrainingSample {
560 features,
561 execution_time_ms: execution_time,
562 memory_usage_mb: resource_snapshot.memory_usage * 100.0,
563 cache_hit: false,
564 error_occurred: !success,
565 timestamp: std::time::SystemTime::now(),
566 };
567 let ml = ml_opt.read().await;
568 let _ = ml.add_training_sample(sample).await;
570 }
571
572 Ok(())
573 }
574
575 async fn get_resource_snapshot(&self) -> Result<ResourceSnapshot> {
577 let mut sys = self.system.write().await;
578 sys.refresh_cpu_all();
579 sys.refresh_memory();
580
581 let cpu_usage = sys
583 .cpus()
584 .iter()
585 .map(|cpu| cpu.cpu_usage() as f64)
586 .sum::<f64>()
587 / sys.cpus().len().max(1) as f64
588 / 100.0;
589
590 let memory_usage = {
591 let total_mem = sys.total_memory();
592 let used_mem = sys.used_memory();
593 if total_mem > 0 {
594 used_mem as f64 / total_mem as f64
595 } else {
596 0.0
597 }
598 };
599
600 let (active_queries, avg_query_time) = {
602 let history = self.execution_history.read().await;
603 let active = history.len();
604
605 let avg_time = if !history.is_empty() {
606 history.iter().map(|r| r.actual_time_ms).sum::<f64>() / history.len() as f64
607 } else {
608 0.0
609 };
610
611 (active, avg_time)
612 };
613
614 Ok(ResourceSnapshot {
615 timestamp: Instant::now(),
616 cpu_usage,
617 memory_usage,
618 active_queries,
619 avg_query_time_ms: avg_query_time,
620 })
621 }
622
623 fn fingerprint_query(query: &str) -> String {
625 use std::collections::hash_map::DefaultHasher;
626 use std::hash::{Hash, Hasher};
627
628 let normalized = query
629 .to_lowercase()
630 .split_whitespace()
631 .collect::<Vec<_>>()
632 .join(" ");
633
634 let mut hasher = DefaultHasher::new();
635 normalized.hash(&mut hasher);
636
637 format!("query_{:x}", hasher.finish())
638 }
639
640 pub async fn get_strategy_stats(&self) -> HashMap<ExecutionStrategy, StrategyStats> {
642 self.strategy_performance.read().await.clone()
643 }
644
645 pub fn get_metrics(&self) -> DynamicPlannerMetrics {
647 DynamicPlannerMetrics {
648 total_adaptations: self.adaptations.get() as usize,
649 strategy_switches: self.strategy_switches.get() as usize,
650 high_load_events: self.high_load_events.get() as usize,
651 avg_adaptation_quality: self.avg_adaptation_quality.get(),
652 }
653 }
654}
655
656#[derive(Debug, Clone, Serialize, Deserialize)]
658pub struct DynamicPlannerMetrics {
659 pub total_adaptations: usize,
660 pub strategy_switches: usize,
661 pub high_load_events: usize,
662 pub avg_adaptation_quality: f64,
663}
664
665#[cfg(test)]
666mod tests {
667 use super::*;
668
669 #[test]
670 fn test_execution_strategy_overhead() {
671 assert_eq!(ExecutionStrategy::Sequential.overhead_multiplier(), 1.0);
672 assert!(ExecutionStrategy::Parallel.overhead_multiplier() > 1.0);
673 assert!(ExecutionStrategy::Batched.overhead_multiplier() < 1.0);
674 assert!(ExecutionStrategy::Cached.overhead_multiplier() < 0.1);
675 }
676
677 #[test]
678 fn test_execution_strategy_high_load_safe() {
679 assert!(ExecutionStrategy::Degraded.is_high_load_safe());
680 assert!(ExecutionStrategy::Cached.is_high_load_safe());
681 assert!(ExecutionStrategy::Sequential.is_high_load_safe());
682 assert!(!ExecutionStrategy::Parallel.is_high_load_safe());
683 }
684
685 #[test]
686 fn test_resource_snapshot_high_load() {
687 let config = DynamicPlannerConfig::default();
688
689 let low_load = ResourceSnapshot {
690 timestamp: Instant::now(),
691 cpu_usage: 0.5,
692 memory_usage: 0.6,
693 active_queries: 10,
694 avg_query_time_ms: 50.0,
695 };
696 assert!(!low_load.is_high_load(&config));
697
698 let high_load = ResourceSnapshot {
699 timestamp: Instant::now(),
700 cpu_usage: 0.9,
701 memory_usage: 0.9,
702 active_queries: 100,
703 avg_query_time_ms: 500.0,
704 };
705 assert!(high_load.is_high_load(&config));
706 }
707
708 #[test]
709 fn test_resource_snapshot_load_severity() {
710 let config = DynamicPlannerConfig::default();
711
712 let snapshot = ResourceSnapshot {
713 timestamp: Instant::now(),
714 cpu_usage: 0.9, memory_usage: 0.7, active_queries: 50,
717 avg_query_time_ms: 100.0,
718 };
719
720 let severity = snapshot.load_severity(&config);
721 assert!(severity > 0.8); }
723
724 #[test]
725 fn test_adaptive_plan_creation() {
726 let plan = AdaptivePlan::new("query_123".to_string(), ExecutionStrategy::Parallel, 50.0);
727
728 assert_eq!(plan.query_fingerprint, "query_123");
729 assert_eq!(plan.strategy, ExecutionStrategy::Parallel);
730 assert_eq!(plan.estimated_cost, 50.0);
731 assert_eq!(plan.confidence, 0.5);
732 }
733
734 #[test]
735 fn test_adaptive_plan_with_fallback() {
736 let plan = AdaptivePlan::new("query_123".to_string(), ExecutionStrategy::Parallel, 50.0)
737 .with_fallback(ExecutionStrategy::Sequential);
738
739 assert_eq!(plan.fallback_strategy, Some(ExecutionStrategy::Sequential));
740 }
741
742 #[test]
743 fn test_adaptive_plan_with_confidence() {
744 let plan = AdaptivePlan::new("query_123".to_string(), ExecutionStrategy::Parallel, 50.0)
745 .with_confidence(0.85);
746
747 assert_eq!(plan.confidence, 0.85);
748
749 let clamped =
751 AdaptivePlan::new("query_456".to_string(), ExecutionStrategy::Sequential, 10.0)
752 .with_confidence(1.5);
753
754 assert_eq!(clamped.confidence, 1.0); }
756
757 #[tokio::test]
758 async fn test_dynamic_planner_creation() {
759 let config = DynamicPlannerConfig::default();
760 let planner = DynamicQueryPlanner::new(config);
761
762 let metrics = planner.get_metrics();
763 assert_eq!(metrics.total_adaptations, 0);
764 assert_eq!(metrics.strategy_switches, 0);
765 }
766
767 #[tokio::test]
768 async fn test_create_plan_disabled() {
769 let config = DynamicPlannerConfig {
770 enabled: false,
771 ..Default::default()
772 };
773 let planner = DynamicQueryPlanner::new(config);
774
775 let plan = planner
776 .create_plan("SELECT * WHERE { ?s ?p ?o }", 10.0)
777 .await
778 .expect("should succeed");
779
780 assert_eq!(plan.strategy, ExecutionStrategy::Sequential);
781 }
782
783 #[tokio::test]
784 async fn test_create_plan_simple_query() {
785 let config = DynamicPlannerConfig {
787 cpu_threshold: 1.0, memory_threshold: 1.0, ..Default::default()
790 };
791 let planner = DynamicQueryPlanner::new(config);
792
793 let plan = planner
794 .create_plan("SELECT ?s WHERE { ?s ?p ?o } LIMIT 10", 5.0)
795 .await
796 .expect("should succeed");
797
798 assert!(
800 !matches!(
801 plan.strategy,
802 ExecutionStrategy::Parallel | ExecutionStrategy::Degraded
803 ),
804 "Simple query used {:?} strategy",
805 plan.strategy
806 );
807
808 assert!(plan.fallback_strategy.is_some());
810 }
811
812 #[tokio::test]
813 async fn test_create_plan_complex_query() {
814 let config = DynamicPlannerConfig {
816 cpu_threshold: 1.0, memory_threshold: 1.0, ..Default::default()
819 };
820 let planner = DynamicQueryPlanner::new(config);
821
822 let complex_query =
823 "SELECT ?s ?p ?o WHERE { ?s ?p ?o . ?s ?p2 ?o2 . ?s ?p3 ?o3 } LIMIT 1000";
824 let plan = planner
825 .create_plan(complex_query, 75.0)
826 .await
827 .expect("should succeed");
828
829 assert!(matches!(
831 plan.strategy,
832 ExecutionStrategy::Optimized | ExecutionStrategy::Parallel
833 ));
834 assert!(plan.fallback_strategy.is_some());
835 }
836
837 #[tokio::test]
838 async fn test_record_execution() {
839 let config = DynamicPlannerConfig::default();
840 let planner = DynamicQueryPlanner::new(config);
841
842 let result = ExecutionResult {
843 query_fingerprint: "query_123".to_string(),
844 strategy_used: ExecutionStrategy::Parallel,
845 actual_time_ms: 150.0,
846 success: true,
847 error_message: None,
848 resource_snapshot: ResourceSnapshot {
849 timestamp: Instant::now(),
850 cpu_usage: 0.6,
851 memory_usage: 0.7,
852 active_queries: 5,
853 avg_query_time_ms: 100.0,
854 },
855 };
856
857 planner
858 .record_execution(result)
859 .await
860 .expect("should succeed");
861
862 let stats = planner.get_strategy_stats().await;
864 let parallel_stats = stats
865 .get(&ExecutionStrategy::Parallel)
866 .expect("should succeed");
867
868 assert_eq!(parallel_stats.total_executions, 1);
869 assert_eq!(parallel_stats.successful_executions, 1);
870 assert_eq!(parallel_stats.avg_time_ms, 150.0);
871 }
872
873 #[tokio::test]
874 async fn test_query_fingerprinting() {
875 let query1 = "SELECT ?s WHERE { ?s ?p ?o }";
876 let query2 = "select ?s where { ?s ?p ?o }"; let query3 = "SELECT ?x WHERE { ?x ?p ?o }"; let fp1 = DynamicQueryPlanner::fingerprint_query(query1);
880 let fp2 = DynamicQueryPlanner::fingerprint_query(query2);
881 let fp3 = DynamicQueryPlanner::fingerprint_query(query3);
882
883 assert_eq!(fp1, fp2);
885
886 assert_ne!(fp1, fp3);
888 }
889
890 #[tokio::test]
891 async fn test_default_strategy_selection() {
892 let config = DynamicPlannerConfig::default();
893 let planner = DynamicQueryPlanner::new(config.clone());
894
895 let low_load = ResourceSnapshot {
896 timestamp: Instant::now(),
897 cpu_usage: 0.3,
898 memory_usage: 0.4,
899 active_queries: 2,
900 avg_query_time_ms: 50.0,
901 };
902
903 let strategy = planner.select_default_strategy(5.0, &low_load);
905 assert_eq!(strategy, ExecutionStrategy::Sequential);
906
907 let strategy = planner.select_default_strategy(25.0, &low_load);
909 assert_eq!(strategy, ExecutionStrategy::Batched);
910
911 let strategy = planner.select_default_strategy(70.0, &low_load);
913 assert_eq!(strategy, ExecutionStrategy::Parallel);
914
915 let high_load = ResourceSnapshot {
917 timestamp: Instant::now(),
918 cpu_usage: 0.95,
919 memory_usage: 0.90,
920 active_queries: 100,
921 avg_query_time_ms: 500.0,
922 };
923
924 let strategy = planner.select_default_strategy(70.0, &high_load);
925 assert_eq!(strategy, ExecutionStrategy::Degraded);
926 }
927
928 #[test]
929 fn test_strategy_stats_default() {
930 let stats = StrategyStats::default();
931 assert_eq!(stats.total_executions, 0);
932 assert_eq!(stats.successful_executions, 0);
933 assert_eq!(stats.error_rate, 0.0);
934 }
935
936 #[test]
937 fn test_config_defaults() {
938 let config = DynamicPlannerConfig::default();
939 assert!(config.enabled);
940 assert!(config.enable_ml_prediction);
941 assert_eq!(config.cpu_threshold, 0.80);
942 assert_eq!(config.memory_threshold, 0.85);
943 }
944
945 #[tokio::test]
946 async fn test_ml_optimizer_wired() {
947 let config = DynamicPlannerConfig {
948 enable_ml_prediction: true,
949 cpu_threshold: 1.0,
950 memory_threshold: 1.0,
951 ..Default::default()
952 };
953 let planner = DynamicQueryPlanner::new(config);
954 let plan = planner
956 .create_plan("SELECT ?s WHERE { ?s ?p ?o }", 10.0)
957 .await
958 .expect("should succeed");
959 assert!(plan.fallback_strategy.is_some());
960 }
961
962 #[tokio::test]
963 async fn test_ml_training_updates() {
964 let config = DynamicPlannerConfig {
965 enable_ml_prediction: true,
966 cpu_threshold: 1.0,
967 memory_threshold: 1.0,
968 ..Default::default()
969 };
970 let planner = DynamicQueryPlanner::new(config);
971
972 let snapshot = ResourceSnapshot {
973 timestamp: Instant::now(),
974 cpu_usage: 0.3,
975 memory_usage: 0.4,
976 active_queries: 5,
977 avg_query_time_ms: 50.0,
978 };
979
980 for i in 0..5u32 {
981 let exec_result = ExecutionResult {
982 query_fingerprint: format!("query_{i}"),
983 strategy_used: ExecutionStrategy::Sequential,
984 actual_time_ms: (i as f64 + 1.0) * 20.0,
985 success: true,
986 error_message: None,
987 resource_snapshot: snapshot.clone(),
988 };
989 planner
990 .record_execution(exec_result)
991 .await
992 .expect("should succeed");
993 }
994
995 let stats = planner.get_strategy_stats().await;
996 let seq_stats = stats
997 .get(&ExecutionStrategy::Sequential)
998 .expect("should have Sequential stats");
999 assert_eq!(seq_stats.total_executions, 5);
1000 assert!(seq_stats.avg_time_ms > 0.0);
1001 }
1002}