Skip to main content

oxirs_arq/
integrated_query_planner.rs

1//! Integrated Query Planner
2//!
3//! This module provides unified integration of all optimization components:
4//! - Index-aware BGP optimization
5//! - Statistics-based cost estimation
6//! - Streaming optimization
7//! - Machine learning-enhanced planning
8//! - Adaptive query execution
9
10use 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};
19// use crate::bgp_optimizer::BGPOptimizer;
20use 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
29/// Integrated query planner combining all optimization techniques
30pub 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/// Configuration for integrated query planning
49#[derive(Debug, Clone)]
50pub struct IntegratedPlannerConfig {
51    /// Enable adaptive optimization based on execution feedback
52    pub adaptive_optimization: bool,
53    /// Enable cross-query optimization
54    pub cross_query_optimization: bool,
55    /// Memory threshold for switching to streaming (bytes)
56    pub streaming_threshold: usize,
57    /// Enable machine learning-enhanced cost estimation
58    pub ml_cost_estimation: bool,
59    /// Plan cache size
60    pub plan_cache_size: usize,
61    /// Enable parallel plan exploration
62    pub parallel_planning: bool,
63    /// Statistics collection interval
64    pub stats_collection_interval: Duration,
65    /// Enable advanced index recommendations
66    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, // 512MB
75            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/// Comprehensive execution plan
85#[derive(Debug, Clone)]
86pub struct IntegratedExecutionPlan {
87    /// Optimized algebra expression
88    pub optimized_algebra: Algebra,
89    /// Estimated execution cost
90    pub estimated_cost: CostEstimate,
91    /// Index usage plan
92    pub index_plan: IndexUsagePlan,
93    /// Whether to use streaming execution
94    pub use_streaming: bool,
95    /// Recommended memory allocation
96    pub memory_allocation: usize,
97    /// Expected execution time
98    pub expected_duration: Duration,
99    /// Confidence in the plan (0.0 to 1.0)
100    pub confidence: f64,
101    /// Adaptive hints for execution
102    pub adaptive_hints: AdaptiveHints,
103    /// Alternative plans for fallback
104    pub alternative_plans: Vec<AlternativePlan>,
105}
106
107/// Adaptive hints for execution tuning
108#[derive(Debug, Clone, Default)]
109pub struct AdaptiveHints {
110    /// Suggested batch size for operations
111    pub batch_size: Option<usize>,
112    /// Suggested parallelism level
113    pub parallelism_level: Option<usize>,
114    /// Memory allocation suggestions
115    pub memory_hints: MemoryHints,
116    /// Index access patterns
117    pub index_access_patterns: Vec<IndexAccessPattern>,
118    /// Join algorithm recommendations
119    pub join_algorithms: Vec<JoinAlgorithmHint>,
120}
121
122/// Memory allocation hints
123#[derive(Debug, Clone, Default)]
124pub struct MemoryHints {
125    /// Minimum memory requirement
126    pub min_memory: usize,
127    /// Optimal memory allocation
128    pub optimal_memory: usize,
129    /// Maximum beneficial memory
130    pub max_memory: usize,
131    /// Memory allocation strategy
132    pub allocation_strategy: MemoryStrategy,
133}
134
135/// Memory allocation strategies
136#[derive(Debug, Clone, Default)]
137pub enum MemoryStrategy {
138    Conservative,
139    #[default]
140    Balanced,
141    Aggressive,
142    Adaptive,
143}
144
145/// Index access pattern hint
146#[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/// Access patterns for index optimization
155#[derive(Debug, Clone)]
156pub enum AccessPattern {
157    Sequential,
158    Random,
159    Clustered,
160    Sparse,
161    Range,
162}
163
164/// Join algorithm hint
165#[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/// Join algorithm types
175#[derive(Debug, Clone)]
176pub enum JoinAlgorithm {
177    HashJoin,
178    SortMergeJoin,
179    NestedLoopJoin,
180    IndexNestedLoopJoin,
181    StreamingHashJoin,
182    SymmetricHashJoin,
183}
184
185/// Alternative execution plan
186#[derive(Debug, Clone)]
187pub struct AlternativePlan {
188    pub plan: IntegratedExecutionPlan,
189    pub trigger_conditions: Vec<TriggerCondition>,
190    pub fallback_priority: usize,
191}
192
193/// Conditions for switching to alternative plans
194#[derive(Debug, Clone)]
195pub enum TriggerCondition {
196    MemoryPressure(f64),
197    ExecutionTimeExceeded(Duration),
198    CardinalityMismatch(f64),
199    IndexUnavailable(IndexType),
200    ConcurrencyLimit,
201}
202
203/// Plan cache for optimization reuse
204#[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/// Cached execution plan with metadata
213#[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/// Triggers for plan cache invalidation
223#[derive(Debug, Clone)]
224pub enum InvalidationTrigger {
225    StatisticsUpdate,
226    IndexChange,
227    DataSizeChange(f64),
228    TimeElapsed(Duration),
229}
230
231/// Execution history for adaptive learning
232#[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/// Record of query execution
241#[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/// Performance metrics for query patterns
257#[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/// Adaptive thresholds that adjust based on system performance
268#[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, // 512MB
281            parallel_execution_threshold: 100.0,           // Cost units
282            index_recommendation_threshold: 0.1,           // 10% improvement
283            plan_cache_accuracy_threshold: 0.8,            // 80% accuracy
284            statistics_staleness_threshold: Duration::from_secs(3600), // 1 hour
285        }
286    }
287}
288
289impl IntegratedQueryPlanner {
290    /// Create a new integrated query planner
291    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        // Create a separate StatisticsCollector for the AdvancedOptimizer to avoid type conflicts
306        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    /// Create an integrated execution plan for a query
341    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        // Check plan cache first
346        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        // Step 1: Analyze query complexity and characteristics
355        let query_analysis = self.analyze_query(algebra)?;
356
357        // Step 2: Collect and update statistics
358        self.update_statistics(&query_analysis)?;
359
360        // Step 3: Optimize BGP patterns with index awareness
361        let optimized_bgp = self.optimize_bgp_patterns(algebra)?;
362
363        // Step 4: Apply advanced optimizations
364        let advanced_optimized = algebra.clone(); // Use algebra directly for now
365
366        // Step 5: Determine execution strategy (streaming vs. in-memory)
367        let execution_strategy =
368            self.determine_execution_strategy(&advanced_optimized, &query_analysis)?;
369
370        // Step 6: Generate cost estimates
371        let cost_estimate =
372            self.estimate_execution_cost(&advanced_optimized, &execution_strategy)?;
373
374        // Step 7: Create adaptive hints
375        let adaptive_hints = self.generate_adaptive_hints(&advanced_optimized, &cost_estimate)?;
376
377        // Step 8: Generate alternative plans
378        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        // Cache the plan
394        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    /// Update execution statistics based on actual performance
406    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, // Would need to be provided
419            plan_hash,
420            actual_duration,
421            estimated_duration: Duration::from_secs(0), // Would need to be retrieved from plan
422            actual_cardinality,
423            estimated_cardinality: 0, // Would need to be retrieved from plan
424            memory_used,
425            index_hits: HashMap::new(),
426            execution_timestamp: Instant::now(),
427            success,
428            error_info,
429        };
430
431        // Update execution history
432        {
433            let mut history = self.execution_history.lock().expect("lock poisoned");
434            history.add_execution(execution_record.clone());
435        }
436
437        // Update adaptive thresholds based on performance
438        self.update_adaptive_thresholds(&execution_record)?;
439
440        // Update cost model with actual vs. estimated performance
441        self.update_cost_model(&execution_record)?;
442
443        debug!("Updated execution feedback for plan {}", plan_hash);
444        Ok(())
445    }
446
447    /// Get recommendations for index creation
448    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    /// Analyze query characteristics for optimization
459    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        // Calculate complexity score
465        analysis.complexity_score = self.calculate_complexity_score(&analysis);
466
467        // Estimate memory requirements
468        analysis.estimated_memory = self.estimate_memory_requirements(&analysis)?;
469
470        Ok(analysis)
471    }
472
473    /// Recursively analyze algebra expression
474    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                // Handle other algebra types
513            }
514        }
515        Ok(())
516    }
517
518    /// Calculate complexity score for query
519    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    /// Extract variables from a triple pattern
541    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    /// Check if filter expression is complex
558    #[allow(clippy::only_used_in_recursion)]
559    fn is_complex_filter(&self, expression: &Expression) -> bool {
560        // Simplified complexity check
561        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    /// Generate adaptive hints for execution
572    fn generate_adaptive_hints(
573        &self,
574        _algebra: &Algebra,
575        cost_estimate: &CostEstimate,
576    ) -> Result<AdaptiveHints> {
577        let mut hints = AdaptiveHints::default();
578
579        // Calculate optimal batch size based on memory and cardinality
580        if cost_estimate.cardinality > 10000 {
581            hints.batch_size = Some((cost_estimate.cardinality / 100).max(1000));
582        }
583
584        // Determine parallelism level
585        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        // Memory allocation hints
595        hints.memory_hints = self.calculate_memory_hints(cost_estimate)?;
596
597        Ok(hints)
598    }
599
600    /// Calculate memory allocation hints
601    fn calculate_memory_hints(&self, cost_estimate: &CostEstimate) -> Result<MemoryHints> {
602        let base_memory = 64 * 1024 * 1024; // 64MB base
603        let cardinality_memory = cost_estimate.cardinality * 100; // ~100 bytes per result
604
605        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    /// Compute hash for algebra expression
614    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    /// Get cached plan if available and valid
624    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    /// Cache execution plan
630    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    /// Calculate confidence in execution plan
637    fn calculate_plan_confidence(&self, _cost_estimate: &CostEstimate) -> Result<f64> {
638        // Base confidence on cost model accuracy and statistics quality
639        let base_confidence = 0.7;
640        let stats_factor = 0.2; // Would be calculated from statistics quality
641        let history_factor = 0.1; // Would be calculated from execution history
642
643        Ok(base_confidence + stats_factor + history_factor)
644    }
645
646    // Additional implementation methods would continue here...
647    // For brevity, I'm including the most important components
648}
649
650/// Query analysis results
651#[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/// Execution strategy determination
666#[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/// Index recommendation
675#[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
684// Implementation of helper structs
685impl 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        // Implement LRU eviction if cache is full
701        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
752/// Implementation placeholder methods for the main struct
753impl IntegratedQueryPlanner {
754    fn update_statistics(&mut self, _analysis: &QueryAnalysis) -> Result<()> {
755        // Update statistics collector with query patterns
756        Ok(())
757    }
758
759    fn optimize_bgp_patterns(&mut self, algebra: &Algebra) -> Result<OptimizedBGP> {
760        // Create BGPOptimizer with required statistics
761        // let _bgp_optimizer = BGPOptimizer::new(&self.statistics, &self.index_stats);
762
763        // Extract BGP patterns from algebra
764        let bgp_patterns = self.extract_bgp_patterns(algebra);
765
766        // Optimize each BGP with the optimizer
767        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            // Calculate pattern selectivity based on statistics
775            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            // Determine index usage for this pattern
795            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, // Estimated scan cost
801                });
802            }
803
804            total_cost += selectivity * 10.0; // Base cost per pattern
805            optimized_patterns.push(pattern.clone());
806        }
807
808        // Calculate join selectivity between patterns
809        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        // Calculate overall selectivity
821        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![], // Would be computed based on join analysis
838                index_intersections: vec![], // Would be computed for complex patterns
839                bloom_filter_candidates: vec![], // Would be suggested for large joins
840                recommended_indices: vec![], // Would be suggested based on patterns
841                access_patterns: vec![], // Would be analyzed from query structure
842                estimated_cost_reduction: 0.0, // Would be computed based on index usage
843            },
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; // Memory cost in MB
870        let network_cost = 0.0;
871
872        // Recursively calculate costs based on algebra structure
873        let estimated_cardinality = match algebra {
874            Algebra::Bgp(patterns) => {
875                // Cost for BGP evaluation
876                cpu_cost += patterns.len() as f64 * 2.0; // Base cost per pattern
877                io_cost += patterns.len() as f64 * 1.0; // I/O cost for pattern matching
878                (patterns.len() * 100).max(1) // Estimate based on pattern count
879            }
880            Algebra::Join { left, right } => {
881                // Recursive cost calculation for joins
882                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                // Join cost is proportional to the product of cardinalities
889                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; // Additional cost for filtering
906                io_cost += pattern_cost.io_cost;
907                (pattern_cost.cardinality as f64 * 0.5) as usize // Filtering reduces cardinality
908            }
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; // Grouping cost
914                io_cost += pattern_cost.io_cost;
915                (pattern_cost.cardinality as f64 * 0.2) as usize // Grouping reduces cardinality
916            }
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; // O(n log n) sort
923                cpu_cost += pattern_cost.cpu_cost + sort_cost;
924                io_cost += pattern_cost.io_cost;
925                pattern_cost.cardinality
926            }
927            _ => {
928                // Default costs for other algebra types
929                cpu_cost += 1.0;
930                io_cost += 0.5;
931                100
932            }
933        };
934
935        // Apply strategy-specific adjustments
936        if strategy.use_streaming {
937            memory_cost *= 0.5; // Streaming reduces memory usage
938            io_cost *= 1.2; // But increases I/O
939        }
940
941        if strategy.parallel_execution {
942            cpu_cost *= 0.7; // Parallel execution improves CPU efficiency
943        }
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; // 64MB
956        let variable_factor = analysis.variables.len() * 1024 * 1024; // 1MB per variable
957        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        // Generate alternative execution plans for fallback
968        Ok(vec![])
969    }
970
971    fn update_adaptive_thresholds(&mut self, record: &ExecutionRecord) -> Result<()> {
972        // Update adaptive thresholds based on execution performance
973        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 our estimates are consistently off, adjust thresholds
980        if accuracy_ratio > 2.0 {
981            // We're underestimating, be more conservative
982            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            // We're overestimating, be more aggressive
987            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        // Update plan cache accuracy threshold based on actual success rate
993        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        // Update cost model with actual vs. estimated performance
1007        let _cost_model = self.cost_model.lock().expect("lock poisoned");
1008
1009        // Calculate estimation error
1010        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        // Update cost model parameters based on errors
1027        // This is a simplified approach - in practice, you'd use more sophisticated ML techniques
1028        if duration_error > 0.5 {
1029            info!(
1030                "Large duration estimation error: {:.2}, updating cost model",
1031                duration_error
1032            );
1033            // Adjust cost factors based on the error
1034        }
1035
1036        if cardinality_error > 0.5 {
1037            info!(
1038                "Large cardinality estimation error: {:.2}, updating statistics",
1039                cardinality_error
1040            );
1041            // Update cardinality estimation parameters
1042        }
1043
1044        // Update statistics collector with actual execution data
1045        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        // Analyze execution history to recommend new indexes
1066        // Basic index recommendations based on common query patterns
1067        // In a full implementation, this would analyze actual execution history
1068
1069        let recommendations = vec![
1070            // Recommend B-tree index for frequently filtered properties
1071            IndexRecommendation {
1072                index_type: IndexType::BTree,
1073                estimated_benefit: 0.3, // 30% improvement
1074                creation_cost: 100.0,
1075                maintenance_cost: 10.0,
1076                confidence: 0.8,
1077            },
1078            // Recommend hash index for equality lookups
1079            IndexRecommendation {
1080                index_type: IndexType::Hash,
1081                estimated_benefit: 0.5, // 50% improvement for exact matches
1082                creation_cost: 50.0,
1083                maintenance_cost: 5.0,
1084                confidence: 0.9,
1085            },
1086        ];
1087
1088        Ok(recommendations)
1089    }
1090
1091    /// Extract BGP patterns from algebra expression
1092    #[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    /// Estimate selectivity of a triple pattern
1112    fn estimate_pattern_selectivity(&self, pattern: &TriplePattern) -> f64 {
1113        // Basic selectivity estimation based on pattern structure
1114        let mut selectivity: f64 = 1.0;
1115
1116        // Reduce selectivity for each concrete term (non-variable)
1117        if !matches!(pattern.subject, Term::Variable(_)) {
1118            selectivity *= 0.1; // Subject specified reduces selectivity to 10%
1119        }
1120        if !matches!(pattern.predicate, Term::Variable(_)) {
1121            selectivity *= 0.2; // Predicate specified reduces selectivity to 20%
1122        }
1123        if !matches!(pattern.object, Term::Variable(_)) {
1124            selectivity *= 0.1; // Object specified reduces selectivity to 10%
1125        }
1126
1127        // Ensure minimum selectivity
1128        selectivity.max(0.001)
1129    }
1130
1131    /// Suggest an index for a specific pattern
1132    fn suggest_index_for_pattern(&self, pattern: &TriplePattern) -> Option<(usize, IndexType)> {
1133        // Suggest index based on pattern characteristics
1134        match (&pattern.subject, &pattern.predicate, &pattern.object) {
1135            // If subject is variable but predicate is concrete, suggest predicate index
1136            (Term::Variable(_), Term::Iri(_), _) => Some((1, IndexType::BTree)),
1137            // If object is concrete, suggest object index
1138            (_, _, Term::Literal(_)) => Some((2, IndexType::Hash)),
1139            // If subject is concrete, suggest subject index
1140            (Term::Iri(_), _, _) => Some((0, IndexType::Hash)),
1141            _ => None,
1142        }
1143    }
1144
1145    /// Find join variables between two patterns
1146    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    /// Estimate join selectivity between two patterns
1154    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; // Cartesian product
1159        }
1160
1161        // Estimate based on number of join variables
1162        // More join variables typically means higher selectivity
1163        match join_vars.len() {
1164            1 => 0.1,  // Single variable join
1165            2 => 0.05, // Two variable join - more selective
1166            _ => 0.01, // Multiple variable joins are very selective
1167        }
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}