1use std::collections::{HashMap, HashSet, VecDeque};
11use std::sync::{Arc, Mutex};
12use std::time::{Duration, Instant};
13
14use anyhow::Result;
15use tracing::{debug, info, span, Level};
16
17use crate::advanced_optimizer::{AdvancedOptimizer, AdvancedOptimizerConfig};
18use crate::algebra::{Algebra, Expression, Term, TriplePattern, Variable};
19use crate::bgp_optimizer_types::{
21 IndexAssignment, IndexUsagePlan, OptimizedBGP, PatternSelectivity, SelectivityFactors,
22 SelectivityInfo,
23};
24use crate::cost_model::{CostEstimate, CostModel};
25use crate::optimizer::{IndexStatistics, IndexType, Statistics};
26use crate::statistics_collector::StatisticsCollector;
27use crate::streaming::{StreamingConfig, StreamingExecutor};
28
29pub struct IntegratedQueryPlanner {
31 config: IntegratedPlannerConfig,
32 cost_model: Arc<Mutex<CostModel>>,
33 #[allow(dead_code)]
34 statistics_collector: Arc<Mutex<StatisticsCollector>>,
35 #[allow(dead_code)]
36 statistics: Statistics,
37 #[allow(dead_code)]
38 index_stats: IndexStatistics,
39 #[allow(dead_code)]
40 advanced_optimizer: AdvancedOptimizer,
41 #[allow(dead_code)]
42 streaming_executor: Option<StreamingExecutor>,
43 plan_cache: Arc<Mutex<PlanCache>>,
44 execution_history: Arc<Mutex<ExecutionHistory>>,
45 adaptive_thresholds: AdaptiveThresholds,
46}
47
48#[derive(Debug, Clone)]
50pub struct IntegratedPlannerConfig {
51 pub adaptive_optimization: bool,
53 pub cross_query_optimization: bool,
55 pub streaming_threshold: usize,
57 pub ml_cost_estimation: bool,
59 pub plan_cache_size: usize,
61 pub parallel_planning: bool,
63 pub stats_collection_interval: Duration,
65 pub advanced_index_recommendations: bool,
67}
68
69impl Default for IntegratedPlannerConfig {
70 fn default() -> Self {
71 Self {
72 adaptive_optimization: true,
73 cross_query_optimization: true,
74 streaming_threshold: 512 * 1024 * 1024, ml_cost_estimation: true,
76 plan_cache_size: 1000,
77 parallel_planning: true,
78 stats_collection_interval: Duration::from_secs(60),
79 advanced_index_recommendations: true,
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct IntegratedExecutionPlan {
87 pub optimized_algebra: Algebra,
89 pub estimated_cost: CostEstimate,
91 pub index_plan: IndexUsagePlan,
93 pub use_streaming: bool,
95 pub memory_allocation: usize,
97 pub expected_duration: Duration,
99 pub confidence: f64,
101 pub adaptive_hints: AdaptiveHints,
103 pub alternative_plans: Vec<AlternativePlan>,
105}
106
107#[derive(Debug, Clone, Default)]
109pub struct AdaptiveHints {
110 pub batch_size: Option<usize>,
112 pub parallelism_level: Option<usize>,
114 pub memory_hints: MemoryHints,
116 pub index_access_patterns: Vec<IndexAccessPattern>,
118 pub join_algorithms: Vec<JoinAlgorithmHint>,
120}
121
122#[derive(Debug, Clone, Default)]
124pub struct MemoryHints {
125 pub min_memory: usize,
127 pub optimal_memory: usize,
129 pub max_memory: usize,
131 pub allocation_strategy: MemoryStrategy,
133}
134
135#[derive(Debug, Clone, Default)]
137pub enum MemoryStrategy {
138 Conservative,
139 #[default]
140 Balanced,
141 Aggressive,
142 Adaptive,
143}
144
145#[derive(Debug, Clone)]
147pub struct IndexAccessPattern {
148 pub index_type: IndexType,
149 pub access_pattern: AccessPattern,
150 pub expected_selectivity: f64,
151 pub prefetch_hint: bool,
152}
153
154#[derive(Debug, Clone)]
156pub enum AccessPattern {
157 Sequential,
158 Random,
159 Clustered,
160 Sparse,
161 Range,
162}
163
164#[derive(Debug, Clone)]
166pub struct JoinAlgorithmHint {
167 pub left_pattern_idx: usize,
168 pub right_pattern_idx: usize,
169 pub recommended_algorithm: JoinAlgorithm,
170 pub estimated_cost: f64,
171 pub memory_requirement: usize,
172}
173
174#[derive(Debug, Clone)]
176pub enum JoinAlgorithm {
177 HashJoin,
178 SortMergeJoin,
179 NestedLoopJoin,
180 IndexNestedLoopJoin,
181 StreamingHashJoin,
182 SymmetricHashJoin,
183}
184
185#[derive(Debug, Clone)]
187pub struct AlternativePlan {
188 pub plan: IntegratedExecutionPlan,
189 pub trigger_conditions: Vec<TriggerCondition>,
190 pub fallback_priority: usize,
191}
192
193#[derive(Debug, Clone)]
195pub enum TriggerCondition {
196 MemoryPressure(f64),
197 ExecutionTimeExceeded(Duration),
198 CardinalityMismatch(f64),
199 IndexUnavailable(IndexType),
200 ConcurrencyLimit,
201}
202
203#[derive(Debug)]
205pub struct PlanCache {
206 plans: HashMap<u64, CachedPlan>,
207 access_counts: HashMap<u64, usize>,
208 last_access: HashMap<u64, Instant>,
209 max_size: usize,
210}
211
212#[derive(Debug, Clone)]
214pub struct CachedPlan {
215 pub plan: IntegratedExecutionPlan,
216 pub creation_time: Instant,
217 pub access_count: usize,
218 pub average_accuracy: f64,
219 pub invalidation_triggers: Vec<InvalidationTrigger>,
220}
221
222#[derive(Debug, Clone)]
224pub enum InvalidationTrigger {
225 StatisticsUpdate,
226 IndexChange,
227 DataSizeChange(f64),
228 TimeElapsed(Duration),
229}
230
231#[derive(Debug)]
233pub struct ExecutionHistory {
234 executions: VecDeque<ExecutionRecord>,
235 #[allow(dead_code)]
236 pattern_performance: HashMap<String, PatternPerformance>,
237 max_history_size: usize,
238}
239
240#[derive(Debug, Clone)]
242pub struct ExecutionRecord {
243 pub query_hash: u64,
244 pub plan_hash: u64,
245 pub actual_duration: Duration,
246 pub estimated_duration: Duration,
247 pub actual_cardinality: usize,
248 pub estimated_cardinality: usize,
249 pub memory_used: usize,
250 pub index_hits: HashMap<IndexType, usize>,
251 pub execution_timestamp: Instant,
252 pub success: bool,
253 pub error_info: Option<String>,
254}
255
256#[derive(Debug, Clone, Default)]
258pub struct PatternPerformance {
259 pub total_executions: usize,
260 pub successful_executions: usize,
261 pub average_accuracy: f64,
262 pub average_duration: Duration,
263 pub best_plan_hash: Option<u64>,
264 pub worst_plan_hash: Option<u64>,
265}
266
267#[derive(Debug, Clone)]
269pub struct AdaptiveThresholds {
270 pub streaming_memory_threshold: usize,
271 pub parallel_execution_threshold: f64,
272 pub index_recommendation_threshold: f64,
273 pub plan_cache_accuracy_threshold: f64,
274 pub statistics_staleness_threshold: Duration,
275}
276
277impl Default for AdaptiveThresholds {
278 fn default() -> Self {
279 Self {
280 streaming_memory_threshold: 512 * 1024 * 1024, parallel_execution_threshold: 100.0, index_recommendation_threshold: 0.1, plan_cache_accuracy_threshold: 0.8, statistics_staleness_threshold: Duration::from_secs(3600), }
286 }
287}
288
289impl IntegratedQueryPlanner {
290 pub fn new(config: IntegratedPlannerConfig) -> Result<Self> {
292 let cost_config = crate::cost_model::CostModelConfig::default();
293 let cost_model = Arc::new(Mutex::new(CostModel::new(cost_config)));
294 let statistics_collector = Arc::new(Mutex::new(StatisticsCollector::new()));
295 let statistics = Statistics::new();
296 let index_stats = IndexStatistics::default();
297
298 let advanced_config = AdvancedOptimizerConfig {
299 enable_ml_optimization: config.ml_cost_estimation,
300 cross_query_optimization: config.cross_query_optimization,
301 parallel_optimization: config.parallel_planning,
302 ..Default::default()
303 };
304
305 let advanced_optimizer_stats = Arc::new(StatisticsCollector::new());
307 let advanced_optimizer = AdvancedOptimizer::new(
308 advanced_config,
309 cost_model.clone(),
310 advanced_optimizer_stats,
311 );
312
313 let streaming_executor = if config.streaming_threshold > 0 {
314 let streaming_config = StreamingConfig {
315 max_memory_usage: config.streaming_threshold,
316 ..Default::default()
317 };
318 Some(StreamingExecutor::new(streaming_config)?)
319 } else {
320 None
321 };
322
323 let plan_cache = Arc::new(Mutex::new(PlanCache::new(config.plan_cache_size)));
324 let execution_history = Arc::new(Mutex::new(ExecutionHistory::new(10000)));
325
326 Ok(Self {
327 config,
328 cost_model,
329 statistics_collector,
330 statistics,
331 index_stats,
332 advanced_optimizer,
333 streaming_executor,
334 plan_cache,
335 execution_history,
336 adaptive_thresholds: AdaptiveThresholds::default(),
337 })
338 }
339
340 pub fn create_plan(&mut self, algebra: &Algebra) -> Result<IntegratedExecutionPlan> {
342 let _span = span!(Level::INFO, "integrated_planning").entered();
343 let start_time = Instant::now();
344
345 let query_hash = self.compute_algebra_hash(algebra);
347 if let Some(cached_plan) = self.get_cached_plan(query_hash) {
348 debug!("Using cached execution plan");
349 return Ok(cached_plan.plan);
350 }
351
352 info!("Creating new integrated execution plan");
353
354 let query_analysis = self.analyze_query(algebra)?;
356
357 self.update_statistics(&query_analysis)?;
359
360 let optimized_bgp = self.optimize_bgp_patterns(algebra)?;
362
363 let advanced_optimized = algebra.clone(); let execution_strategy =
368 self.determine_execution_strategy(&advanced_optimized, &query_analysis)?;
369
370 let cost_estimate =
372 self.estimate_execution_cost(&advanced_optimized, &execution_strategy)?;
373
374 let adaptive_hints = self.generate_adaptive_hints(&advanced_optimized, &cost_estimate)?;
376
377 let alternative_plans =
379 self.generate_alternative_plans(&advanced_optimized, &cost_estimate)?;
380
381 let plan = IntegratedExecutionPlan {
382 optimized_algebra: advanced_optimized,
383 estimated_cost: cost_estimate.clone(),
384 index_plan: optimized_bgp.index_plan,
385 use_streaming: execution_strategy.use_streaming,
386 memory_allocation: execution_strategy.memory_allocation,
387 expected_duration: Duration::from_millis((cost_estimate.total_cost * 10.0) as u64),
388 confidence: self.calculate_plan_confidence(&cost_estimate)?,
389 adaptive_hints,
390 alternative_plans,
391 };
392
393 self.cache_plan(query_hash, plan.clone())?;
395
396 let planning_time = start_time.elapsed();
397 info!(
398 "Plan created in {:?} with confidence {:.2}",
399 planning_time, plan.confidence
400 );
401
402 Ok(plan)
403 }
404
405 pub fn update_execution_feedback(
407 &mut self,
408 plan_hash: u64,
409 actual_duration: Duration,
410 actual_cardinality: usize,
411 memory_used: usize,
412 success: bool,
413 error_info: Option<String>,
414 ) -> Result<()> {
415 let _span = span!(Level::DEBUG, "execution_feedback").entered();
416
417 let execution_record = ExecutionRecord {
418 query_hash: 0, plan_hash,
420 actual_duration,
421 estimated_duration: Duration::from_secs(0), actual_cardinality,
423 estimated_cardinality: 0, memory_used,
425 index_hits: HashMap::new(),
426 execution_timestamp: Instant::now(),
427 success,
428 error_info,
429 };
430
431 {
433 let mut history = self.execution_history.lock().expect("lock poisoned");
434 history.add_execution(execution_record.clone());
435 }
436
437 self.update_adaptive_thresholds(&execution_record)?;
439
440 self.update_cost_model(&execution_record)?;
442
443 debug!("Updated execution feedback for plan {}", plan_hash);
444 Ok(())
445 }
446
447 pub fn get_index_recommendations(&self) -> Result<Vec<IndexRecommendation>> {
449 let _span = span!(Level::INFO, "index_recommendations").entered();
450
451 let history = self.execution_history.lock().expect("lock poisoned");
452 let recommendations = self.analyze_index_opportunities(&history)?;
453
454 info!("Generated {} index recommendations", recommendations.len());
455 Ok(recommendations)
456 }
457
458 fn analyze_query(&self, algebra: &Algebra) -> Result<QueryAnalysis> {
460 let mut analysis = QueryAnalysis::default();
461
462 self.analyze_algebra_recursive(algebra, &mut analysis)?;
463
464 analysis.complexity_score = self.calculate_complexity_score(&analysis);
466
467 analysis.estimated_memory = self.estimate_memory_requirements(&analysis)?;
469
470 Ok(analysis)
471 }
472
473 fn analyze_algebra_recursive(
475 &self,
476 algebra: &Algebra,
477 analysis: &mut QueryAnalysis,
478 ) -> Result<()> {
479 match algebra {
480 Algebra::Bgp(patterns) => {
481 analysis.triple_pattern_count += patterns.len();
482 for pattern in patterns {
483 analysis
484 .variables
485 .extend(self.extract_pattern_variables(pattern));
486 }
487 }
488 Algebra::Join { left, right } => {
489 analysis.join_count += 1;
490 self.analyze_algebra_recursive(left, analysis)?;
491 self.analyze_algebra_recursive(right, analysis)?;
492 }
493 Algebra::Union { left, right } => {
494 analysis.union_count += 1;
495 self.analyze_algebra_recursive(left, analysis)?;
496 self.analyze_algebra_recursive(right, analysis)?;
497 }
498 Algebra::Filter { pattern, condition } => {
499 analysis.filter_count += 1;
500 analysis.has_complex_filters = self.is_complex_filter(condition);
501 self.analyze_algebra_recursive(pattern, analysis)?;
502 }
503 Algebra::Group { pattern, .. } => {
504 analysis.has_aggregation = true;
505 self.analyze_algebra_recursive(pattern, analysis)?;
506 }
507 Algebra::OrderBy { pattern, .. } => {
508 analysis.has_sorting = true;
509 self.analyze_algebra_recursive(pattern, analysis)?;
510 }
511 _ => {
512 }
514 }
515 Ok(())
516 }
517
518 fn calculate_complexity_score(&self, analysis: &QueryAnalysis) -> f64 {
520 let mut score = 0.0;
521
522 score += analysis.triple_pattern_count as f64 * 1.0;
523 score += analysis.join_count as f64 * 5.0;
524 score += analysis.union_count as f64 * 3.0;
525 score += analysis.filter_count as f64 * 2.0;
526
527 if analysis.has_aggregation {
528 score += 10.0;
529 }
530 if analysis.has_sorting {
531 score += 8.0;
532 }
533 if analysis.has_complex_filters {
534 score += 5.0;
535 }
536
537 score
538 }
539
540 fn extract_pattern_variables(&self, pattern: &TriplePattern) -> HashSet<Variable> {
542 let mut variables = HashSet::new();
543
544 if let Term::Variable(var) = &pattern.subject {
545 variables.insert(var.clone());
546 }
547 if let Term::Variable(var) = &pattern.predicate {
548 variables.insert(var.clone());
549 }
550 if let Term::Variable(var) = &pattern.object {
551 variables.insert(var.clone());
552 }
553
554 variables
555 }
556
557 #[allow(clippy::only_used_in_recursion)]
559 fn is_complex_filter(&self, expression: &Expression) -> bool {
560 match expression {
562 Expression::Function { .. } => true,
563 Expression::Exists(_) | Expression::NotExists(_) => true,
564 Expression::Binary { left, right, .. } => {
565 self.is_complex_filter(left) || self.is_complex_filter(right)
566 }
567 _ => false,
568 }
569 }
570
571 fn generate_adaptive_hints(
573 &self,
574 _algebra: &Algebra,
575 cost_estimate: &CostEstimate,
576 ) -> Result<AdaptiveHints> {
577 let mut hints = AdaptiveHints::default();
578
579 if cost_estimate.cardinality > 10000 {
581 hints.batch_size = Some((cost_estimate.cardinality / 100).max(1000));
582 }
583
584 if cost_estimate.total_cost > 100.0 {
586 hints.parallelism_level = Some(
587 std::thread::available_parallelism()
588 .map(|n| n.get())
589 .unwrap_or(1)
590 .min(4),
591 );
592 }
593
594 hints.memory_hints = self.calculate_memory_hints(cost_estimate)?;
596
597 Ok(hints)
598 }
599
600 fn calculate_memory_hints(&self, cost_estimate: &CostEstimate) -> Result<MemoryHints> {
602 let base_memory = 64 * 1024 * 1024; let cardinality_memory = cost_estimate.cardinality * 100; Ok(MemoryHints {
606 min_memory: base_memory,
607 optimal_memory: base_memory + cardinality_memory,
608 max_memory: (base_memory + cardinality_memory) * 2,
609 allocation_strategy: MemoryStrategy::Balanced,
610 })
611 }
612
613 fn compute_algebra_hash(&self, algebra: &Algebra) -> u64 {
615 use std::collections::hash_map::DefaultHasher;
616 use std::hash::{Hash, Hasher};
617
618 let mut hasher = DefaultHasher::new();
619 format!("{algebra:?}").hash(&mut hasher);
620 hasher.finish()
621 }
622
623 fn get_cached_plan(&self, query_hash: u64) -> Option<CachedPlan> {
625 let cache = self.plan_cache.lock().expect("lock poisoned");
626 cache.get_plan(query_hash)
627 }
628
629 fn cache_plan(&self, query_hash: u64, plan: IntegratedExecutionPlan) -> Result<()> {
631 let mut cache = self.plan_cache.lock().expect("lock poisoned");
632 cache.insert_plan(query_hash, plan);
633 Ok(())
634 }
635
636 fn calculate_plan_confidence(&self, _cost_estimate: &CostEstimate) -> Result<f64> {
638 let base_confidence = 0.7;
640 let stats_factor = 0.2; let history_factor = 0.1; Ok(base_confidence + stats_factor + history_factor)
644 }
645
646 }
649
650#[derive(Debug, Default)]
652pub struct QueryAnalysis {
653 pub triple_pattern_count: usize,
654 pub join_count: usize,
655 pub union_count: usize,
656 pub filter_count: usize,
657 pub variables: HashSet<Variable>,
658 pub has_aggregation: bool,
659 pub has_sorting: bool,
660 pub has_complex_filters: bool,
661 pub complexity_score: f64,
662 pub estimated_memory: usize,
663}
664
665#[derive(Debug)]
667pub struct ExecutionStrategy {
668 pub use_streaming: bool,
669 pub memory_allocation: usize,
670 pub parallel_execution: bool,
671 pub index_recommendations: Vec<IndexType>,
672}
673
674#[derive(Debug, Clone)]
676pub struct IndexRecommendation {
677 pub index_type: IndexType,
678 pub estimated_benefit: f64,
679 pub creation_cost: f64,
680 pub maintenance_cost: f64,
681 pub confidence: f64,
682}
683
684impl PlanCache {
686 fn new(max_size: usize) -> Self {
687 Self {
688 plans: HashMap::new(),
689 access_counts: HashMap::new(),
690 last_access: HashMap::new(),
691 max_size,
692 }
693 }
694
695 fn get_plan(&self, query_hash: u64) -> Option<CachedPlan> {
696 self.plans.get(&query_hash).cloned()
697 }
698
699 fn insert_plan(&mut self, query_hash: u64, plan: IntegratedExecutionPlan) {
700 if self.plans.len() >= self.max_size {
702 self.evict_lru();
703 }
704
705 let cached_plan = CachedPlan {
706 plan,
707 creation_time: Instant::now(),
708 access_count: 0,
709 average_accuracy: 0.0,
710 invalidation_triggers: vec![
711 InvalidationTrigger::TimeElapsed(Duration::from_secs(3600)),
712 InvalidationTrigger::StatisticsUpdate,
713 ],
714 };
715
716 self.plans.insert(query_hash, cached_plan);
717 self.access_counts.insert(query_hash, 0);
718 self.last_access.insert(query_hash, Instant::now());
719 }
720
721 fn evict_lru(&mut self) {
722 if let Some(oldest_key) = self
723 .last_access
724 .iter()
725 .min_by_key(|&(_, &instant)| instant)
726 .map(|(&key, _)| key)
727 {
728 self.plans.remove(&oldest_key);
729 self.access_counts.remove(&oldest_key);
730 self.last_access.remove(&oldest_key);
731 }
732 }
733}
734
735impl ExecutionHistory {
736 fn new(max_size: usize) -> Self {
737 Self {
738 executions: VecDeque::new(),
739 pattern_performance: HashMap::new(),
740 max_history_size: max_size,
741 }
742 }
743
744 fn add_execution(&mut self, record: ExecutionRecord) {
745 if self.executions.len() >= self.max_history_size {
746 self.executions.pop_front();
747 }
748 self.executions.push_back(record);
749 }
750}
751
752impl IntegratedQueryPlanner {
754 fn update_statistics(&mut self, _analysis: &QueryAnalysis) -> Result<()> {
755 Ok(())
757 }
758
759 fn optimize_bgp_patterns(&mut self, algebra: &Algebra) -> Result<OptimizedBGP> {
760 let bgp_patterns = self.extract_bgp_patterns(algebra);
765
766 let mut optimized_patterns = Vec::new();
768 let mut total_cost = 0.0;
769 let mut pattern_selectivity = Vec::new();
770 let mut join_selectivity = HashMap::new();
771 let mut pattern_indexes = Vec::new();
772
773 for pattern in &bgp_patterns {
774 let selectivity = self.estimate_pattern_selectivity(pattern);
776 let cardinality = (1_000_000.0 * selectivity).max(1.0) as usize;
777
778 let pattern_sel = PatternSelectivity {
779 pattern: pattern.clone(),
780 selectivity,
781 cardinality,
782 factors: SelectivityFactors {
783 subject_selectivity: 1.0,
784 predicate_selectivity: 1.0,
785 object_selectivity: 1.0,
786 type_selectivity: 1.0,
787 literal_selectivity: 1.0,
788 index_factor: 1.0,
789 distribution_factor: 1.0,
790 },
791 };
792 pattern_selectivity.push(pattern_sel);
793
794 let index_hint = self.suggest_index_for_pattern(pattern);
796 if let Some((pattern_idx, index_type)) = index_hint {
797 pattern_indexes.push(IndexAssignment {
798 pattern_idx,
799 index_type,
800 scan_cost: selectivity * 5.0, });
802 }
803
804 total_cost += selectivity * 10.0; optimized_patterns.push(pattern.clone());
806 }
807
808 for i in 0..bgp_patterns.len() {
810 for j in i + 1..bgp_patterns.len() {
811 let join_vars = self.find_join_variables(&bgp_patterns[i], &bgp_patterns[j]);
812 if !join_vars.is_empty() {
813 let selectivity =
814 self.estimate_join_selectivity(&bgp_patterns[i], &bgp_patterns[j]);
815 join_selectivity.insert((i, j), selectivity);
816 }
817 }
818 }
819
820 let overall_selectivity = pattern_selectivity
822 .iter()
823 .map(|p| p.selectivity)
824 .product::<f64>()
825 * join_selectivity.values().product::<f64>();
826
827 Ok(OptimizedBGP {
828 patterns: optimized_patterns,
829 estimated_cost: total_cost,
830 selectivity_info: SelectivityInfo {
831 pattern_selectivity,
832 join_selectivity,
833 overall_selectivity,
834 },
835 index_plan: IndexUsagePlan {
836 pattern_indexes,
837 join_indexes: vec![], index_intersections: vec![], bloom_filter_candidates: vec![], recommended_indices: vec![], access_patterns: vec![], estimated_cost_reduction: 0.0, },
844 })
845 }
846
847 fn determine_execution_strategy(
848 &self,
849 _algebra: &Algebra,
850 analysis: &QueryAnalysis,
851 ) -> Result<ExecutionStrategy> {
852 Ok(ExecutionStrategy {
853 use_streaming: analysis.estimated_memory > self.config.streaming_threshold,
854 memory_allocation: analysis.estimated_memory,
855 parallel_execution: analysis.complexity_score
856 > self.adaptive_thresholds.parallel_execution_threshold,
857 index_recommendations: vec![],
858 })
859 }
860
861 #[allow(clippy::only_used_in_recursion)]
862 fn estimate_execution_cost(
863 &self,
864 algebra: &Algebra,
865 strategy: &ExecutionStrategy,
866 ) -> Result<CostEstimate> {
867 let mut cpu_cost = 0.0;
868 let mut io_cost = 0.0;
869 let mut memory_cost = strategy.memory_allocation as f64 / 1024.0 / 1024.0; let network_cost = 0.0;
871
872 let estimated_cardinality = match algebra {
874 Algebra::Bgp(patterns) => {
875 cpu_cost += patterns.len() as f64 * 2.0; io_cost += patterns.len() as f64 * 1.0; (patterns.len() * 100).max(1) }
880 Algebra::Join { left, right } => {
881 let left_cost = self.estimate_execution_cost(left, strategy)?;
883 let right_cost = self.estimate_execution_cost(right, strategy)?;
884
885 cpu_cost += left_cost.cpu_cost + right_cost.cpu_cost;
886 io_cost += left_cost.io_cost + right_cost.io_cost;
887
888 let join_cost = (left_cost.cardinality * right_cost.cardinality) as f64 * 0.001;
890 cpu_cost += join_cost;
891
892 ((left_cost.cardinality as f64 * right_cost.cardinality as f64 * 0.1) as usize)
893 .max(1)
894 }
895 Algebra::Union { left, right } => {
896 let left_cost = self.estimate_execution_cost(left, strategy)?;
897 let right_cost = self.estimate_execution_cost(right, strategy)?;
898
899 cpu_cost += left_cost.cpu_cost + right_cost.cpu_cost;
900 io_cost += left_cost.io_cost + right_cost.io_cost;
901 left_cost.cardinality + right_cost.cardinality
902 }
903 Algebra::Filter { pattern, .. } => {
904 let pattern_cost = self.estimate_execution_cost(pattern, strategy)?;
905 cpu_cost += pattern_cost.cpu_cost + 5.0; io_cost += pattern_cost.io_cost;
907 (pattern_cost.cardinality as f64 * 0.5) as usize }
909 Algebra::Group {
910 pattern, variables, ..
911 } => {
912 let pattern_cost = self.estimate_execution_cost(pattern, strategy)?;
913 cpu_cost += pattern_cost.cpu_cost + variables.len() as f64 * 3.0; io_cost += pattern_cost.io_cost;
915 (pattern_cost.cardinality as f64 * 0.2) as usize }
917 Algebra::OrderBy {
918 pattern,
919 conditions,
920 } => {
921 let pattern_cost = self.estimate_execution_cost(pattern, strategy)?;
922 let sort_cost = (pattern_cost.cardinality as f64).log2() * conditions.len() as f64; cpu_cost += pattern_cost.cpu_cost + sort_cost;
924 io_cost += pattern_cost.io_cost;
925 pattern_cost.cardinality
926 }
927 _ => {
928 cpu_cost += 1.0;
930 io_cost += 0.5;
931 100
932 }
933 };
934
935 if strategy.use_streaming {
937 memory_cost *= 0.5; io_cost *= 1.2; }
940
941 if strategy.parallel_execution {
942 cpu_cost *= 0.7; }
944
945 Ok(CostEstimate::new(
946 cpu_cost,
947 io_cost,
948 memory_cost,
949 network_cost,
950 estimated_cardinality,
951 ))
952 }
953
954 fn estimate_memory_requirements(&self, analysis: &QueryAnalysis) -> Result<usize> {
955 let base_memory = 64 * 1024 * 1024; let variable_factor = analysis.variables.len() * 1024 * 1024; let complexity_factor = (analysis.complexity_score * 1024.0 * 1024.0) as usize;
958
959 Ok(base_memory + variable_factor + complexity_factor)
960 }
961
962 fn generate_alternative_plans(
963 &self,
964 _algebra: &Algebra,
965 _cost_estimate: &CostEstimate,
966 ) -> Result<Vec<AlternativePlan>> {
967 Ok(vec![])
969 }
970
971 fn update_adaptive_thresholds(&mut self, record: &ExecutionRecord) -> Result<()> {
972 let accuracy_ratio = if record.estimated_duration.as_millis() > 0 {
974 record.actual_duration.as_millis() as f64 / record.estimated_duration.as_millis() as f64
975 } else {
976 1.0
977 };
978
979 if accuracy_ratio > 2.0 {
981 self.adaptive_thresholds.streaming_memory_threshold =
983 (self.adaptive_thresholds.streaming_memory_threshold as f64 * 1.1) as usize;
984 self.adaptive_thresholds.parallel_execution_threshold *= 1.1;
985 } else if accuracy_ratio < 0.5 {
986 self.adaptive_thresholds.streaming_memory_threshold =
988 (self.adaptive_thresholds.streaming_memory_threshold as f64 * 0.9) as usize;
989 self.adaptive_thresholds.parallel_execution_threshold *= 0.9;
990 }
991
992 if record.success {
994 self.adaptive_thresholds.plan_cache_accuracy_threshold =
995 (self.adaptive_thresholds.plan_cache_accuracy_threshold * 0.95 + 0.05).min(0.95);
996 } else {
997 self.adaptive_thresholds.plan_cache_accuracy_threshold =
998 (self.adaptive_thresholds.plan_cache_accuracy_threshold * 0.95).max(0.5);
999 }
1000
1001 debug!("Updated adaptive thresholds based on execution feedback");
1002 Ok(())
1003 }
1004
1005 fn update_cost_model(&mut self, record: &ExecutionRecord) -> Result<()> {
1006 let _cost_model = self.cost_model.lock().expect("lock poisoned");
1008
1009 let duration_error = if record.estimated_duration.as_millis() > 0 {
1011 (record.actual_duration.as_millis() as f64
1012 - record.estimated_duration.as_millis() as f64)
1013 .abs()
1014 / record.estimated_duration.as_millis() as f64
1015 } else {
1016 0.0
1017 };
1018
1019 let cardinality_error = if record.estimated_cardinality > 0 {
1020 (record.actual_cardinality as f64 - record.estimated_cardinality as f64).abs()
1021 / record.estimated_cardinality as f64
1022 } else {
1023 0.0
1024 };
1025
1026 if duration_error > 0.5 {
1029 info!(
1030 "Large duration estimation error: {:.2}, updating cost model",
1031 duration_error
1032 );
1033 }
1035
1036 if cardinality_error > 0.5 {
1037 info!(
1038 "Large cardinality estimation error: {:.2}, updating statistics",
1039 cardinality_error
1040 );
1041 }
1043
1044 if let Ok(mut stats_collector) = self.statistics_collector.lock() {
1046 if let Err(e) = stats_collector.update_execution_statistics(
1047 record.actual_duration,
1048 record.actual_cardinality,
1049 record.memory_used,
1050 ) {
1051 tracing::warn!("Failed to update execution statistics: {}", e);
1052 }
1053 } else {
1054 tracing::warn!("Failed to acquire lock for statistics collector");
1055 }
1056
1057 debug!("Updated cost model with execution feedback");
1058 Ok(())
1059 }
1060
1061 fn analyze_index_opportunities(
1062 &self,
1063 _history: &ExecutionHistory,
1064 ) -> Result<Vec<IndexRecommendation>> {
1065 let recommendations = vec![
1070 IndexRecommendation {
1072 index_type: IndexType::BTree,
1073 estimated_benefit: 0.3, creation_cost: 100.0,
1075 maintenance_cost: 10.0,
1076 confidence: 0.8,
1077 },
1078 IndexRecommendation {
1080 index_type: IndexType::Hash,
1081 estimated_benefit: 0.5, creation_cost: 50.0,
1083 maintenance_cost: 5.0,
1084 confidence: 0.9,
1085 },
1086 ];
1087
1088 Ok(recommendations)
1089 }
1090
1091 #[allow(clippy::only_used_in_recursion)]
1093 fn extract_bgp_patterns(&self, algebra: &Algebra) -> Vec<TriplePattern> {
1094 match algebra {
1095 Algebra::Bgp(patterns) => patterns.clone(),
1096 Algebra::Join { left, right } => {
1097 let mut patterns = self.extract_bgp_patterns(left);
1098 patterns.extend(self.extract_bgp_patterns(right));
1099 patterns
1100 }
1101 Algebra::Union { left, right } => {
1102 let mut patterns = self.extract_bgp_patterns(left);
1103 patterns.extend(self.extract_bgp_patterns(right));
1104 patterns
1105 }
1106 Algebra::Filter { pattern, .. } => self.extract_bgp_patterns(pattern),
1107 _ => Vec::new(),
1108 }
1109 }
1110
1111 fn estimate_pattern_selectivity(&self, pattern: &TriplePattern) -> f64 {
1113 let mut selectivity: f64 = 1.0;
1115
1116 if !matches!(pattern.subject, Term::Variable(_)) {
1118 selectivity *= 0.1; }
1120 if !matches!(pattern.predicate, Term::Variable(_)) {
1121 selectivity *= 0.2; }
1123 if !matches!(pattern.object, Term::Variable(_)) {
1124 selectivity *= 0.1; }
1126
1127 selectivity.max(0.001)
1129 }
1130
1131 fn suggest_index_for_pattern(&self, pattern: &TriplePattern) -> Option<(usize, IndexType)> {
1133 match (&pattern.subject, &pattern.predicate, &pattern.object) {
1135 (Term::Variable(_), Term::Iri(_), _) => Some((1, IndexType::BTree)),
1137 (_, _, Term::Literal(_)) => Some((2, IndexType::Hash)),
1139 (Term::Iri(_), _, _) => Some((0, IndexType::Hash)),
1141 _ => None,
1142 }
1143 }
1144
1145 fn find_join_variables(&self, left: &TriplePattern, right: &TriplePattern) -> Vec<Variable> {
1147 let left_vars = self.extract_pattern_variables(left);
1148 let right_vars = self.extract_pattern_variables(right);
1149
1150 left_vars.intersection(&right_vars).cloned().collect()
1151 }
1152
1153 fn estimate_join_selectivity(&self, left: &TriplePattern, right: &TriplePattern) -> f64 {
1155 let join_vars = self.find_join_variables(left, right);
1156
1157 if join_vars.is_empty() {
1158 return 1.0; }
1160
1161 match join_vars.len() {
1164 1 => 0.1, 2 => 0.05, _ => 0.01, }
1168 }
1169}
1170
1171#[cfg(test)]
1172mod tests {
1173 use super::*;
1174
1175 #[test]
1176 fn test_integrated_planner_creation() {
1177 let config = IntegratedPlannerConfig::default();
1178 let planner = IntegratedQueryPlanner::new(config);
1179 assert!(planner.is_ok());
1180 }
1181
1182 #[test]
1183 fn test_query_analysis() {
1184 let config = IntegratedPlannerConfig::default();
1185 let planner = IntegratedQueryPlanner::new(config).unwrap();
1186
1187 let algebra = Algebra::Bgp(vec![]);
1188 let analysis = planner.analyze_query(&algebra).unwrap();
1189
1190 assert_eq!(analysis.triple_pattern_count, 0);
1191 assert_eq!(analysis.join_count, 0);
1192 }
1193
1194 #[test]
1195 fn test_plan_cache() {
1196 let mut cache = PlanCache::new(10);
1197
1198 let plan = IntegratedExecutionPlan {
1199 optimized_algebra: Algebra::Bgp(vec![]),
1200 estimated_cost: CostEstimate {
1201 cpu_cost: 10.0,
1202 io_cost: 5.0,
1203 memory_cost: 1.0,
1204 network_cost: 0.0,
1205 total_cost: 16.0,
1206 cardinality: 1000,
1207 selectivity: 1.0,
1208 operation_costs: HashMap::new(),
1209 },
1210 index_plan: IndexUsagePlan {
1211 pattern_indexes: vec![],
1212 join_indexes: vec![],
1213 index_intersections: vec![],
1214 bloom_filter_candidates: vec![],
1215 recommended_indices: vec![],
1216 access_patterns: vec![],
1217 estimated_cost_reduction: 0.0,
1218 },
1219 use_streaming: false,
1220 memory_allocation: 1024,
1221 expected_duration: Duration::from_millis(100),
1222 confidence: 0.8,
1223 adaptive_hints: AdaptiveHints::default(),
1224 alternative_plans: vec![],
1225 };
1226
1227 cache.insert_plan(12345, plan);
1228 assert!(cache.get_plan(12345).is_some());
1229 }
1230}