Skip to main content

oxirs_arq/
query_analysis.rs

1//! Query Analysis Module
2//!
3//! Provides variable discovery, join variable identification, filter safety analysis,
4//! and semantic validation for SPARQL queries.
5
6use crate::algebra::{Algebra, Expression, Term, TriplePattern, Variable};
7use crate::cost_model::{CostEstimate, CostModel, IOPattern};
8use crate::statistics_collector::StatisticsCollector;
9use anyhow::Result;
10use std::collections::{HashMap, HashSet};
11
12/// Index type for optimization
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub enum IndexType {
15    /// B+ tree index for ordered access
16    BTree,
17    /// Hash index for equality access
18    Hash,
19    /// Full-text search index
20    FullText,
21    /// Spatial index for geographic data
22    Spatial,
23    /// Custom index type
24    Custom(String),
25}
26
27/// Index access method recommendation
28#[derive(Debug, Clone)]
29pub struct IndexAccess {
30    /// The index type to use
31    pub index_type: IndexType,
32    /// Triple pattern position (Subject=0, Predicate=1, Object=2)
33    pub pattern_position: usize,
34    /// Pattern index in the BGP
35    pub pattern_index: usize,
36    /// Expected selectivity with this index
37    pub selectivity: f64,
38    /// Estimated cost with this index
39    pub cost_estimate: CostEstimate,
40    /// I/O pattern for this access
41    pub io_pattern: IOPattern,
42    /// Improvement ratio compared to full scan
43    pub improvement_ratio: f64,
44}
45
46/// Index availability analysis for a pattern
47#[derive(Debug, Clone)]
48pub struct PatternIndexAnalysis {
49    /// Available indexes for this pattern
50    pub available_indexes: Vec<IndexAccess>,
51    /// Recommended index access method
52    pub recommended_access: Option<IndexAccess>,
53    /// Estimated cardinality without index
54    pub full_scan_cardinality: usize,
55    /// Estimated cardinality with best index
56    pub indexed_cardinality: usize,
57    /// Performance improvement ratio
58    pub improvement_ratio: f64,
59}
60
61/// Index-aware optimization recommendations
62#[derive(Debug, Clone)]
63pub struct IndexOptimizationHints {
64    /// Pattern-specific index recommendations
65    pub pattern_recommendations: HashMap<usize, PatternIndexAnalysis>,
66    /// Join order recommendations based on index availability
67    pub join_order_hints: Vec<JoinOrderHint>,
68    /// Filter placement recommendations
69    pub filter_placement_hints: Vec<FilterPlacementHint>,
70    /// Overall query execution strategy
71    pub execution_strategy: ExecutionStrategy,
72}
73
74/// Join order hint based on index analysis
75#[derive(Debug, Clone)]
76pub struct JoinOrderHint {
77    /// Pattern indices in recommended order
78    pub pattern_order: Vec<usize>,
79    /// Expected total cost
80    pub estimated_cost: CostEstimate,
81    /// Reasoning for this order
82    pub reasoning: String,
83}
84
85/// Filter placement optimization hint
86#[derive(Debug, Clone)]
87pub struct FilterPlacementHint {
88    /// Filter expression
89    pub filter: Expression,
90    /// Recommended placement (pattern index)
91    pub recommended_placement: usize,
92    /// Expected selectivity
93    pub selectivity: f64,
94    /// Cost benefit of early placement
95    pub cost_benefit: f64,
96}
97
98/// Execution strategy recommendation
99#[derive(Debug, Clone)]
100pub enum ExecutionStrategy {
101    /// Sequential pattern-by-pattern execution
102    Sequential,
103    /// Parallel execution of independent patterns
104    Parallel,
105    /// Index-driven execution
106    IndexDriven,
107    /// Hash-join based execution
108    HashJoin,
109    /// Sort-merge join execution
110    SortMergeJoin,
111    /// Adaptive execution based on runtime feedback
112    Adaptive,
113}
114
115/// Filter safety analysis results
116#[derive(Debug, Clone)]
117pub struct FilterSafetyAnalysis {
118    /// Safe filters (can be evaluated without error)
119    pub safe_filters: Vec<Expression>,
120    /// Unsafe filters (may produce errors)
121    pub unsafe_filters: Vec<Expression>,
122    /// Filter dependencies on variables
123    pub filter_dependencies: Vec<(Expression, HashSet<Variable>)>,
124}
125
126/// Query analysis results
127#[derive(Debug, Clone)]
128pub struct QueryAnalysis {
129    /// All variables discovered in the query
130    pub variables: HashSet<Variable>,
131    /// Variables that appear in projection
132    pub projected_variables: HashSet<Variable>,
133    /// Variables that appear in filters
134    pub filter_variables: HashSet<Variable>,
135    /// Variables that join patterns together (simplified for tests)
136    pub join_variables: HashSet<Variable>,
137    /// Variable scoping information
138    pub variable_scopes: HashMap<Variable, VariableScope>,
139    /// Filter safety analysis results
140    pub filter_safety: FilterSafetyAnalysis,
141    /// Type consistency analysis
142    pub type_consistency: TypeConsistencyAnalysis,
143    /// Index optimization hints
144    pub index_hints: IndexOptimizationHints,
145    /// Pattern cardinality estimates
146    pub pattern_cardinalities: HashMap<usize, usize>,
147    /// Semantic validation results
148    pub validation_errors: Vec<ValidationError>,
149}
150
151/// Variable scope information
152#[derive(Debug, Clone)]
153pub struct VariableScope {
154    /// Pattern indices where this variable appears
155    pub pattern_indices: HashSet<usize>,
156    /// Whether the variable is bound (not free)
157    pub is_bound: bool,
158    /// Whether the variable appears in projection
159    pub in_projection: bool,
160    /// Whether the variable appears in filters
161    pub in_filters: bool,
162    /// Whether the variable appears in GROUP BY
163    pub in_group_by: bool,
164    /// Whether the variable appears in ORDER BY
165    pub in_order_by: bool,
166}
167
168/// Filter safety classification
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum FilterSafety {
171    /// Safe to push down (no side effects)
172    Safe,
173    /// Unsafe due to optional patterns
174    UnsafeOptional,
175    /// Unsafe due to unbound variables
176    UnsafeUnbound,
177    /// Unsafe due to aggregate functions
178    UnsafeAggregate,
179    /// Unsafe due to service calls
180    UnsafeService,
181}
182
183/// Type consistency analysis
184#[derive(Debug, Clone)]
185pub struct TypeConsistencyAnalysis {
186    /// Type constraints for variables
187    pub variable_types: HashMap<Variable, VariableType>,
188    /// Type errors found
189    pub type_errors: Vec<TypeError>,
190    /// Type warnings
191    pub type_warnings: Vec<TypeWarning>,
192}
193
194/// Variable type information
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub enum VariableType {
197    /// Resource (IRI or Blank Node)
198    Resource,
199    /// Literal value
200    Literal,
201    /// Numeric literal
202    Numeric,
203    /// String literal
204    String,
205    /// Boolean literal
206    Boolean,
207    /// Date/time literal
208    DateTime,
209    /// Unknown or mixed type
210    Unknown,
211}
212
213/// Type error
214#[derive(Debug, Clone)]
215pub struct TypeError {
216    /// Variable involved in the error
217    pub variable: Variable,
218    /// Expected type
219    pub expected: VariableType,
220    /// Actual type
221    pub actual: VariableType,
222    /// Location of the error
223    pub location: String,
224    /// Error message
225    pub message: String,
226}
227
228/// Type warning
229#[derive(Debug, Clone)]
230pub struct TypeWarning {
231    /// Variable involved in the warning
232    pub variable: Variable,
233    /// Warning message
234    pub message: String,
235    /// Location of the warning
236    pub location: String,
237}
238
239/// Validation error
240#[derive(Debug, Clone)]
241pub struct ValidationError {
242    /// Error type
243    pub error_type: ValidationErrorType,
244    /// Error message
245    pub message: String,
246    /// Location where error occurred
247    pub location: String,
248    /// Suggested fix (if any)
249    pub suggestion: Option<String>,
250}
251
252/// Validation error types
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub enum ValidationErrorType {
255    /// Unbound variable in projection
256    UnboundVariable,
257    /// Type mismatch
258    TypeMismatch,
259    /// Invalid aggregate usage
260    InvalidAggregate,
261    /// Invalid service clause
262    InvalidService,
263    /// Circular dependency
264    CircularDependency,
265    /// Semantic inconsistency
266    SemanticInconsistency,
267}
268
269/// Query analyzer
270#[derive(Debug, Clone)]
271pub struct QueryAnalyzer {
272    /// Statistics collector for cardinality estimation
273    #[allow(dead_code)]
274    statistics: Option<StatisticsCollector>,
275    /// Cost model for optimization decisions
276    #[allow(dead_code)]
277    cost_model: Option<CostModel>,
278    /// Whether to enable type inference
279    pub enable_type_inference: bool,
280}
281
282impl QueryAnalyzer {
283    /// Create a new query analyzer
284    pub fn new() -> Self {
285        Self {
286            statistics: None,
287            cost_model: None,
288            enable_type_inference: true,
289        }
290    }
291
292    /// Create analyzer with statistics collector
293    pub fn with_statistics(statistics: StatisticsCollector) -> Self {
294        Self {
295            statistics: Some(statistics),
296            cost_model: None,
297            enable_type_inference: true,
298        }
299    }
300
301    /// Create analyzer with cost model
302    pub fn with_cost_model(cost_model: CostModel) -> Self {
303        Self {
304            statistics: None,
305            cost_model: Some(cost_model),
306            enable_type_inference: true,
307        }
308    }
309
310    /// Create analyzer with both statistics and cost model
311    pub fn with_statistics_and_cost_model(
312        statistics: StatisticsCollector,
313        cost_model: CostModel,
314    ) -> Self {
315        Self {
316            statistics: Some(statistics),
317            cost_model: Some(cost_model),
318            enable_type_inference: true,
319        }
320    }
321
322    /// Analyze a query and return comprehensive analysis results
323    pub fn analyze_query(&self, algebra: &Algebra) -> Result<QueryAnalysis> {
324        let variables = self.discover_variables(algebra)?;
325        let projected_variables = self.extract_projected_variables(algebra);
326        let filter_variables = self.extract_filter_variables(algebra);
327        let join_variables = self.identify_join_variables_simplified(algebra)?;
328        let variable_scopes = self.analyze_variable_scopes(algebra)?;
329        let filter_safety = self.analyze_filter_safety_structured(algebra)?;
330        let type_consistency = self.analyze_type_consistency(algebra)?;
331        let index_hints = self.generate_index_hints(algebra)?;
332        let pattern_cardinalities = self.estimate_pattern_cardinalities(algebra);
333        let validation_errors = self.validate_semantics(algebra)?;
334
335        Ok(QueryAnalysis {
336            variables,
337            projected_variables,
338            filter_variables,
339            join_variables,
340            variable_scopes,
341            filter_safety,
342            type_consistency,
343            index_hints,
344            pattern_cardinalities,
345            validation_errors,
346        })
347    }
348
349    /// Alias for analyze_query for backward compatibility
350    pub fn analyze(&self, algebra: &Algebra) -> Result<QueryAnalysis> {
351        self.analyze_query(algebra)
352    }
353
354    /// Add index information for optimization (placeholder implementation)
355    pub fn add_index(&mut self, _predicate: &str, _index_type: IndexType) {
356        // This is a placeholder implementation
357        // In a real implementation, this would store index information
358        // for use in optimization decisions
359    }
360
361    /// Estimate pattern cardinality based on bound terms
362    pub fn estimate_pattern_cardinality(&self, pattern: &TriplePattern) -> usize {
363        let mut bound_terms = 0;
364
365        if !matches!(&pattern.subject, Term::Variable(_)) {
366            bound_terms += 1;
367        }
368        if !matches!(&pattern.predicate, Term::Variable(_)) {
369            bound_terms += 1;
370        }
371        if !matches!(&pattern.object, Term::Variable(_)) {
372            bound_terms += 1;
373        }
374
375        // Simple heuristic: more bound terms = lower cardinality
376        match bound_terms {
377            0 => 1_000_000, // All variables
378            1 => 100_000,   // One bound term
379            2 => 1_000,     // Two bound terms
380            3 => 1,         // All bound terms
381            _ => 1,
382        }
383    }
384
385    /// Estimate pattern cardinalities for all patterns
386    pub fn estimate_pattern_cardinalities(&self, algebra: &Algebra) -> HashMap<usize, usize> {
387        let mut cardinalities = HashMap::new();
388        let patterns = self.extract_bgp_patterns(algebra);
389
390        for (idx, pattern) in patterns.iter().enumerate() {
391            let cardinality = self.estimate_pattern_cardinality(pattern);
392            cardinalities.insert(idx, cardinality);
393        }
394
395        cardinalities
396    }
397
398    /// Identify join variables in simplified form (returns HashSet)
399    pub fn identify_join_variables_simplified(
400        &self,
401        algebra: &Algebra,
402    ) -> Result<HashSet<Variable>> {
403        let join_vars_detailed = self.identify_join_variables(algebra)?;
404        // Convert to simple HashSet of variables that appear in multiple patterns
405        Ok(join_vars_detailed.into_keys().collect())
406    }
407
408    /// Analyze filter safety and return structured results
409    pub fn analyze_filter_safety_structured(
410        &self,
411        algebra: &Algebra,
412    ) -> Result<FilterSafetyAnalysis> {
413        let safety_vec = self.analyze_filter_safety(algebra)?;
414        let mut safe_filters = Vec::new();
415        let mut unsafe_filters = Vec::new();
416        let mut filter_dependencies = Vec::new();
417
418        for (expr, safety) in safety_vec {
419            match safety {
420                FilterSafety::Safe => safe_filters.push(expr.clone()),
421                _ => unsafe_filters.push(expr.clone()),
422            }
423
424            // Extract variable dependencies from expression
425            let mut deps = HashSet::new();
426            self.collect_variables_from_expression(&expr, &mut deps)
427                .unwrap_or(());
428            filter_dependencies.push((expr, deps));
429        }
430
431        Ok(FilterSafetyAnalysis {
432            safe_filters,
433            unsafe_filters,
434            filter_dependencies,
435        })
436    }
437
438    /// Discover all variables in the algebra expression
439    pub fn discover_variables(&self, algebra: &Algebra) -> Result<HashSet<Variable>> {
440        let mut variables = HashSet::new();
441        self.collect_variables_recursive(algebra, &mut variables)?;
442        Ok(variables)
443    }
444
445    /// Recursively collect variables from algebra
446    fn collect_variables_recursive(
447        &self,
448        algebra: &Algebra,
449        variables: &mut HashSet<Variable>,
450    ) -> Result<()> {
451        match algebra {
452            Algebra::Bgp(patterns) => {
453                for pattern in patterns {
454                    self.collect_variables_from_pattern(pattern, variables)?;
455                }
456            }
457            Algebra::Join { left, right } => {
458                self.collect_variables_recursive(left, variables)?;
459                self.collect_variables_recursive(right, variables)?;
460            }
461            Algebra::Union { left, right } => {
462                self.collect_variables_recursive(left, variables)?;
463                self.collect_variables_recursive(right, variables)?;
464            }
465            Algebra::Filter { pattern, condition } => {
466                self.collect_variables_recursive(pattern, variables)?;
467                self.collect_variables_from_expression(condition, variables)?;
468            }
469            Algebra::Project {
470                pattern,
471                variables: proj_vars,
472            } => {
473                self.collect_variables_recursive(pattern, variables)?;
474                for var in proj_vars {
475                    variables.insert(var.clone());
476                }
477            }
478            Algebra::Group {
479                pattern,
480                variables: group_vars,
481                ..
482            } => {
483                self.collect_variables_recursive(pattern, variables)?;
484                for group_var in group_vars {
485                    self.collect_variables_from_group_condition(group_var, variables)?;
486                }
487            }
488            _ => {
489                // Handle other algebra types as needed
490            }
491        }
492        Ok(())
493    }
494
495    /// Collect variables from a triple pattern
496    fn collect_variables_from_pattern(
497        &self,
498        pattern: &TriplePattern,
499        variables: &mut HashSet<Variable>,
500    ) -> Result<()> {
501        if let Term::Variable(var) = &pattern.subject {
502            variables.insert(var.clone());
503        }
504        if let Term::Variable(var) = &pattern.predicate {
505            variables.insert(var.clone());
506        }
507        if let Term::Variable(var) = &pattern.object {
508            variables.insert(var.clone());
509        }
510        Ok(())
511    }
512
513    /// Collect variables from an expression
514    #[allow(clippy::only_used_in_recursion)]
515    fn collect_variables_from_expression(
516        &self,
517        expr: &Expression,
518        variables: &mut HashSet<Variable>,
519    ) -> Result<()> {
520        match expr {
521            Expression::Variable(var) => {
522                variables.insert(var.clone());
523            }
524            Expression::Binary { left, right, .. } => {
525                self.collect_variables_from_expression(left, variables)?;
526                self.collect_variables_from_expression(right, variables)?;
527            }
528            Expression::Unary { operand, .. } => {
529                self.collect_variables_from_expression(operand, variables)?;
530            }
531            Expression::Function { args, .. } => {
532                for arg in args {
533                    self.collect_variables_from_expression(arg, variables)?;
534                }
535            }
536            _ => {
537                // Handle other expression types as needed
538            }
539        }
540        Ok(())
541    }
542
543    /// Collect variables from group condition
544    fn collect_variables_from_group_condition(
545        &self,
546        _condition: &crate::algebra::GroupCondition,
547        _variables: &mut HashSet<Variable>,
548    ) -> Result<()> {
549        // Implementation depends on GroupCondition structure
550        // For now, assume it contains an expression
551        // This would need to be adjusted based on actual GroupCondition definition
552        Ok(())
553    }
554
555    /// Extract variables that appear in projection
556    pub fn extract_projected_variables(&self, algebra: &Algebra) -> HashSet<Variable> {
557        let mut projected_vars = HashSet::new();
558        if let Algebra::Project { variables, .. } = algebra {
559            for var in variables {
560                projected_vars.insert(var.clone());
561            }
562        }
563        projected_vars
564    }
565
566    /// Extract variables that appear in filters
567    pub fn extract_filter_variables(&self, algebra: &Algebra) -> HashSet<Variable> {
568        let mut filter_vars = HashSet::new();
569        self.extract_filter_variables_recursive(algebra, &mut filter_vars);
570        filter_vars
571    }
572
573    /// Recursively extract filter variables
574    fn extract_filter_variables_recursive(
575        &self,
576        algebra: &Algebra,
577        filter_vars: &mut HashSet<Variable>,
578    ) {
579        match algebra {
580            Algebra::Filter { pattern, condition } => {
581                self.extract_filter_variables_recursive(pattern, filter_vars);
582                self.collect_variables_from_expression(condition, filter_vars)
583                    .unwrap_or(());
584            }
585            Algebra::Join { left, right } => {
586                self.extract_filter_variables_recursive(left, filter_vars);
587                self.extract_filter_variables_recursive(right, filter_vars);
588            }
589            Algebra::Union { left, right } => {
590                self.extract_filter_variables_recursive(left, filter_vars);
591                self.extract_filter_variables_recursive(right, filter_vars);
592            }
593            _ => {} // Other algebra types don't directly contain filters
594        }
595    }
596
597    /// Identify variables that join patterns together
598    pub fn identify_join_variables(
599        &self,
600        algebra: &Algebra,
601    ) -> Result<HashMap<Variable, Vec<usize>>> {
602        let mut join_vars = HashMap::new();
603        let patterns = self.extract_bgp_patterns(algebra);
604
605        for (pattern_idx, pattern) in patterns.iter().enumerate() {
606            let pattern_vars = self.get_pattern_variables(pattern);
607            for var in pattern_vars {
608                join_vars
609                    .entry(var)
610                    .or_insert_with(Vec::new)
611                    .push(pattern_idx);
612            }
613        }
614
615        // Filter to only include variables that appear in multiple patterns
616        join_vars.retain(|_var, pattern_indices| pattern_indices.len() > 1);
617
618        Ok(join_vars)
619    }
620
621    /// Extract BGP patterns from algebra
622    fn extract_bgp_patterns(&self, algebra: &Algebra) -> Vec<TriplePattern> {
623        let mut patterns = Vec::new();
624        self.extract_bgp_patterns_recursive(algebra, &mut patterns);
625        patterns
626    }
627
628    /// Recursively extract BGP patterns
629    #[allow(clippy::only_used_in_recursion)]
630    fn extract_bgp_patterns_recursive(&self, algebra: &Algebra, patterns: &mut Vec<TriplePattern>) {
631        match algebra {
632            Algebra::Bgp(bgp_patterns) => {
633                patterns.extend(bgp_patterns.clone());
634            }
635            Algebra::Join { left, right } => {
636                self.extract_bgp_patterns_recursive(left, patterns);
637                self.extract_bgp_patterns_recursive(right, patterns);
638            }
639            Algebra::Union { left, right } => {
640                self.extract_bgp_patterns_recursive(left, patterns);
641                self.extract_bgp_patterns_recursive(right, patterns);
642            }
643            Algebra::Filter { pattern, .. } => {
644                self.extract_bgp_patterns_recursive(pattern, patterns);
645            }
646            _ => {} // Other types don't contain BGP patterns directly
647        }
648    }
649
650    /// Get variables from a triple pattern
651    fn get_pattern_variables(&self, pattern: &TriplePattern) -> Vec<Variable> {
652        let mut vars = Vec::new();
653        if let Term::Variable(var) = &pattern.subject {
654            vars.push(var.clone());
655        }
656        if let Term::Variable(var) = &pattern.predicate {
657            vars.push(var.clone());
658        }
659        if let Term::Variable(var) = &pattern.object {
660            vars.push(var.clone());
661        }
662        vars
663    }
664
665    /// Analyze variable scoping
666    pub fn analyze_variable_scopes(
667        &self,
668        algebra: &Algebra,
669    ) -> Result<HashMap<Variable, VariableScope>> {
670        let mut scopes = HashMap::new();
671        let all_vars = self.discover_variables(algebra)?;
672
673        for var in all_vars {
674            let scope = VariableScope {
675                pattern_indices: self.find_pattern_indices_for_variable(&var, algebra),
676                is_bound: self.is_variable_bound(&var, algebra),
677                in_projection: self.is_in_projection(&var, algebra),
678                in_filters: self.is_in_filters(&var, algebra),
679                in_group_by: self.is_in_group_by(&var, algebra),
680                in_order_by: self.is_in_order_by(&var, algebra),
681            };
682            scopes.insert(var, scope);
683        }
684
685        Ok(scopes)
686    }
687
688    /// Find pattern indices where a variable appears
689    fn find_pattern_indices_for_variable(
690        &self,
691        var: &Variable,
692        algebra: &Algebra,
693    ) -> HashSet<usize> {
694        let mut indices = HashSet::new();
695        let patterns = self.extract_bgp_patterns(algebra);
696
697        for (idx, pattern) in patterns.iter().enumerate() {
698            if self.pattern_contains_variable(pattern, var) {
699                indices.insert(idx);
700            }
701        }
702
703        indices
704    }
705
706    /// Check if pattern contains variable
707    fn pattern_contains_variable(&self, pattern: &TriplePattern, var: &Variable) -> bool {
708        matches!(&pattern.subject, Term::Variable(v) if v == var)
709            || matches!(&pattern.predicate, Term::Variable(v) if v == var)
710            || matches!(&pattern.object, Term::Variable(v) if v == var)
711    }
712
713    /// Check if variable is bound
714    fn is_variable_bound(&self, var: &Variable, algebra: &Algebra) -> bool {
715        // A variable is bound if it appears in a triple pattern
716        let patterns = self.extract_bgp_patterns(algebra);
717        patterns
718            .iter()
719            .any(|pattern| self.pattern_contains_variable(pattern, var))
720    }
721
722    /// Check if variable is in projection
723    fn is_in_projection(&self, var: &Variable, algebra: &Algebra) -> bool {
724        if let Algebra::Project { variables, .. } = algebra {
725            variables.contains(var)
726        } else {
727            false
728        }
729    }
730
731    /// Check if variable is in filters
732    fn is_in_filters(&self, var: &Variable, algebra: &Algebra) -> bool {
733        let filter_vars = self.extract_filter_variables(algebra);
734        filter_vars.contains(var)
735    }
736
737    /// Check if variable is in GROUP BY
738    fn is_in_group_by(&self, _var: &Variable, _algebra: &Algebra) -> bool {
739        // Implementation depends on how GROUP BY variables are represented
740        // This is a placeholder
741        false
742    }
743
744    /// Check if variable is in ORDER BY
745    fn is_in_order_by(&self, _var: &Variable, _algebra: &Algebra) -> bool {
746        // Implementation depends on how ORDER BY variables are represented
747        // This is a placeholder
748        false
749    }
750
751    /// Analyze filter safety
752    pub fn analyze_filter_safety(
753        &self,
754        algebra: &Algebra,
755    ) -> Result<Vec<(Expression, FilterSafety)>> {
756        let mut safety_vec = Vec::new();
757        self.analyze_filter_safety_recursive(algebra, &mut safety_vec)?;
758        Ok(safety_vec)
759    }
760
761    /// Recursively analyze filter safety
762    fn analyze_filter_safety_recursive(
763        &self,
764        algebra: &Algebra,
765        safety_vec: &mut Vec<(Expression, FilterSafety)>,
766    ) -> Result<()> {
767        match algebra {
768            Algebra::Filter { pattern, condition } => {
769                let safety = self.determine_filter_safety(condition, pattern)?;
770                safety_vec.push((condition.clone(), safety));
771                self.analyze_filter_safety_recursive(pattern, safety_vec)?;
772            }
773            Algebra::Join { left, right } => {
774                self.analyze_filter_safety_recursive(left, safety_vec)?;
775                self.analyze_filter_safety_recursive(right, safety_vec)?;
776            }
777            Algebra::Union { left, right } => {
778                self.analyze_filter_safety_recursive(left, safety_vec)?;
779                self.analyze_filter_safety_recursive(right, safety_vec)?;
780            }
781            _ => {} // Other types handled as needed
782        }
783        Ok(())
784    }
785
786    /// Determine filter safety
787    fn determine_filter_safety(
788        &self,
789        condition: &Expression,
790        context: &Algebra,
791    ) -> Result<FilterSafety> {
792        // Check for various safety conditions
793        if self.contains_aggregate_function(condition) {
794            return Ok(FilterSafety::UnsafeAggregate);
795        }
796
797        if self.contains_service_call(condition) {
798            return Ok(FilterSafety::UnsafeService);
799        }
800
801        if self.has_unbound_variables(condition, context) {
802            return Ok(FilterSafety::UnsafeUnbound);
803        }
804
805        if self.in_optional_context(context) {
806            return Ok(FilterSafety::UnsafeOptional);
807        }
808
809        Ok(FilterSafety::Safe)
810    }
811
812    /// Check if expression contains aggregate function
813    #[allow(clippy::only_used_in_recursion)]
814    fn contains_aggregate_function(&self, expr: &Expression) -> bool {
815        match expr {
816            Expression::Function { name, .. } => {
817                // Check if function name is an aggregate function
818                matches!(
819                    name.as_str(),
820                    "COUNT" | "SUM" | "AVG" | "MIN" | "MAX" | "GROUP_CONCAT"
821                )
822            }
823            Expression::Binary { left, right, .. } => {
824                self.contains_aggregate_function(left) || self.contains_aggregate_function(right)
825            }
826            Expression::Unary { operand, .. } => self.contains_aggregate_function(operand),
827            _ => false,
828        }
829    }
830
831    /// Check if expression contains service call
832    fn contains_service_call(&self, _expr: &Expression) -> bool {
833        // Implementation depends on how service calls are represented
834        // This is a placeholder
835        false
836    }
837
838    /// Check if expression has unbound variables
839    fn has_unbound_variables(&self, expr: &Expression, context: &Algebra) -> bool {
840        let expr_vars = {
841            let mut vars = HashSet::new();
842            self.collect_variables_from_expression(expr, &mut vars)
843                .unwrap_or(());
844            vars
845        };
846
847        let bound_vars = self.discover_variables(context).unwrap_or_default();
848
849        !expr_vars.iter().all(|var| bound_vars.contains(var))
850    }
851
852    /// Check if context is optional
853    fn in_optional_context(&self, _context: &Algebra) -> bool {
854        // Implementation depends on how optional patterns are represented
855        // This is a placeholder
856        false
857    }
858
859    /// Analyze type consistency
860    pub fn analyze_type_consistency(&self, algebra: &Algebra) -> Result<TypeConsistencyAnalysis> {
861        let variables = self.discover_variables(algebra)?;
862        let mut variable_types = HashMap::new();
863        let mut type_errors = Vec::new();
864        let mut type_warnings = Vec::new();
865
866        for var in variables {
867            let var_type = self.infer_variable_type(&var, algebra)?;
868            variable_types.insert(var, var_type);
869        }
870
871        // Analyze type consistency and detect errors
872        self.detect_type_errors(algebra, &variable_types, &mut type_errors)?;
873        self.detect_type_warnings(algebra, &variable_types, &mut type_warnings)?;
874
875        Ok(TypeConsistencyAnalysis {
876            variable_types,
877            type_errors,
878            type_warnings,
879        })
880    }
881
882    /// Infer variable type from usage
883    fn infer_variable_type(&self, var: &Variable, algebra: &Algebra) -> Result<VariableType> {
884        // Analyze how the variable is used to infer its type
885        let patterns = self.extract_bgp_patterns(algebra);
886
887        for pattern in patterns {
888            if matches!(&pattern.subject, Term::Variable(v) if v == var) {
889                return Ok(VariableType::Resource); // Subject is always a resource
890            }
891            if matches!(&pattern.predicate, Term::Variable(v) if v == var) {
892                return Ok(VariableType::Resource); // Predicate is always a resource
893            }
894            if matches!(&pattern.object, Term::Variable(v) if v == var) {
895                // Object can be resource or literal, need more analysis
896                return Ok(VariableType::Unknown);
897            }
898        }
899
900        Ok(VariableType::Unknown)
901    }
902
903    /// Detect type errors
904    fn detect_type_errors(
905        &self,
906        _algebra: &Algebra,
907        _variable_types: &HashMap<Variable, VariableType>,
908        _type_errors: &mut [TypeError],
909    ) -> Result<()> {
910        // Implementation for detecting type errors
911        // This is a placeholder for more sophisticated type checking
912        Ok(())
913    }
914
915    /// Detect type warnings
916    fn detect_type_warnings(
917        &self,
918        _algebra: &Algebra,
919        _variable_types: &HashMap<Variable, VariableType>,
920        _type_warnings: &mut [TypeWarning],
921    ) -> Result<()> {
922        // Implementation for detecting type warnings
923        // This is a placeholder
924        Ok(())
925    }
926
927    /// Generate index optimization hints
928    pub fn generate_index_hints(&self, algebra: &Algebra) -> Result<IndexOptimizationHints> {
929        let patterns = self.extract_bgp_patterns(algebra);
930        let mut pattern_recommendations = HashMap::new();
931
932        for (idx, pattern) in patterns.iter().enumerate() {
933            let analysis = self.analyze_pattern_for_indexes(pattern, idx)?;
934            pattern_recommendations.insert(idx, analysis);
935        }
936
937        let join_order_hints = self.generate_join_order_hints(&patterns)?;
938        let filter_placement_hints = self.generate_filter_placement_hints(algebra)?;
939        let execution_strategy = self.recommend_execution_strategy(algebra)?;
940
941        Ok(IndexOptimizationHints {
942            pattern_recommendations,
943            join_order_hints,
944            filter_placement_hints,
945            execution_strategy,
946        })
947    }
948
949    /// Analyze pattern for index usage
950    fn analyze_pattern_for_indexes(
951        &self,
952        pattern: &TriplePattern,
953        pattern_idx: usize,
954    ) -> Result<PatternIndexAnalysis> {
955        let mut available_indexes = Vec::new();
956
957        // Analyze what indexes could be used for this pattern
958        if !matches!(&pattern.subject, Term::Variable(_)) {
959            // Subject is bound, can use subject index
960            available_indexes.push(IndexAccess {
961                index_type: IndexType::BTree,
962                pattern_position: 0,
963                pattern_index: pattern_idx,
964                selectivity: 0.1, // Estimated
965                cost_estimate: CostEstimate::new(100.0, 10.0, 50.0, 0.0, 1000),
966                io_pattern: IOPattern::Sequential,
967                improvement_ratio: 10.0,
968            });
969        }
970
971        if !matches!(&pattern.predicate, Term::Variable(_)) {
972            // Predicate is bound, can use predicate index
973            available_indexes.push(IndexAccess {
974                index_type: IndexType::Hash,
975                pattern_position: 1,
976                pattern_index: pattern_idx,
977                selectivity: 0.05, // Predicates usually more selective
978                cost_estimate: CostEstimate::new(50.0, 5.0, 25.0, 0.0, 500),
979                io_pattern: IOPattern::Random,
980                improvement_ratio: 20.0,
981            });
982        }
983
984        let recommended_access = available_indexes
985            .iter()
986            .min_by(|a, b| {
987                a.cost_estimate
988                    .total_cost
989                    .partial_cmp(&b.cost_estimate.total_cost)
990                    .unwrap_or(std::cmp::Ordering::Equal)
991            })
992            .cloned();
993
994        Ok(PatternIndexAnalysis {
995            available_indexes,
996            recommended_access,
997            full_scan_cardinality: 1000000, // Placeholder
998            indexed_cardinality: 1000,      // Placeholder
999            improvement_ratio: 1000.0,      // Placeholder
1000        })
1001    }
1002
1003    /// Generate join order hints
1004    fn generate_join_order_hints(&self, patterns: &[TriplePattern]) -> Result<Vec<JoinOrderHint>> {
1005        let mut hints = Vec::new();
1006
1007        if patterns.len() > 1 {
1008            // Generate a simple hint based on pattern selectivity
1009            let order: Vec<usize> = (0..patterns.len()).collect();
1010            hints.push(JoinOrderHint {
1011                pattern_order: order,
1012                estimated_cost: CostEstimate::new(1000.0, 100.0, 500.0, 0.0, 1000),
1013                reasoning: "Default left-deep join order".to_string(),
1014            });
1015        }
1016
1017        Ok(hints)
1018    }
1019
1020    /// Generate filter placement hints
1021    fn generate_filter_placement_hints(
1022        &self,
1023        _algebra: &Algebra,
1024    ) -> Result<Vec<FilterPlacementHint>> {
1025        let hints = Vec::new();
1026        // Implementation for filter placement analysis
1027        // This is a placeholder
1028        Ok(hints)
1029    }
1030
1031    /// Recommend execution strategy
1032    fn recommend_execution_strategy(&self, algebra: &Algebra) -> Result<ExecutionStrategy> {
1033        let complexity = self.estimate_query_complexity(algebra);
1034
1035        if complexity < 10.0 {
1036            Ok(ExecutionStrategy::Sequential)
1037        } else if complexity < 50.0 {
1038            Ok(ExecutionStrategy::IndexDriven)
1039        } else if complexity < 100.0 {
1040            Ok(ExecutionStrategy::HashJoin)
1041        } else {
1042            Ok(ExecutionStrategy::Parallel)
1043        }
1044    }
1045
1046    /// Estimate query complexity
1047    #[allow(clippy::only_used_in_recursion)]
1048    fn estimate_query_complexity(&self, algebra: &Algebra) -> f64 {
1049        match algebra {
1050            Algebra::Bgp(patterns) => patterns.len() as f64,
1051            Algebra::Join { left, right } => {
1052                self.estimate_query_complexity(left) + self.estimate_query_complexity(right) + 10.0
1053            }
1054            Algebra::Union { left, right } => {
1055                self.estimate_query_complexity(left) + self.estimate_query_complexity(right) + 5.0
1056            }
1057            Algebra::Filter { pattern, .. } => self.estimate_query_complexity(pattern) + 2.0,
1058            _ => 1.0,
1059        }
1060    }
1061
1062    /// Validate query semantics
1063    pub fn validate_semantics(&self, algebra: &Algebra) -> Result<Vec<ValidationError>> {
1064        let mut errors = Vec::new();
1065
1066        // Check for unbound variables in projection
1067        self.check_unbound_variables_in_projection(algebra, &mut errors)?;
1068
1069        // Check for invalid aggregates
1070        self.check_invalid_aggregates(algebra, &mut errors)?;
1071
1072        // Check for other semantic issues
1073        self.check_semantic_consistency(algebra, &mut errors)?;
1074
1075        Ok(errors)
1076    }
1077
1078    /// Check for unbound variables in projection
1079    fn check_unbound_variables_in_projection(
1080        &self,
1081        algebra: &Algebra,
1082        errors: &mut Vec<ValidationError>,
1083    ) -> Result<()> {
1084        if let Algebra::Project { variables, pattern } = algebra {
1085            let bound_vars = self.discover_variables(pattern)?;
1086
1087            for var in variables {
1088                if !bound_vars.contains(var) {
1089                    errors.push(ValidationError {
1090                        error_type: ValidationErrorType::UnboundVariable,
1091                        message: format!(
1092                            "Variable ?{} appears in projection but is not bound",
1093                            var.as_str()
1094                        ),
1095                        location: "SELECT clause".to_string(),
1096                        suggestion: Some(format!(
1097                            "Ensure ?{} appears in a triple pattern",
1098                            var.as_str()
1099                        )),
1100                    });
1101                }
1102            }
1103        }
1104        Ok(())
1105    }
1106
1107    /// Check for invalid aggregates
1108    fn check_invalid_aggregates(
1109        &self,
1110        _algebra: &Algebra,
1111        _errors: &mut [ValidationError],
1112    ) -> Result<()> {
1113        // Implementation for aggregate validation
1114        // This is a placeholder
1115        Ok(())
1116    }
1117
1118    /// Check semantic consistency
1119    fn check_semantic_consistency(
1120        &self,
1121        _algebra: &Algebra,
1122        _errors: &mut [ValidationError],
1123    ) -> Result<()> {
1124        // Implementation for semantic consistency checks
1125        // This is a placeholder
1126        Ok(())
1127    }
1128}
1129
1130impl Default for QueryAnalyzer {
1131    fn default() -> Self {
1132        Self::new()
1133    }
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138    use super::*;
1139    use crate::algebra::{BinaryOperator, Literal, Term, Variable};
1140    use oxirs_core::model::NamedNode;
1141
1142    #[test]
1143    fn test_query_analyzer() {
1144        let analyzer = QueryAnalyzer::new();
1145        assert!(analyzer.enable_type_inference);
1146    }
1147
1148    #[test]
1149    fn test_variable_discovery() {
1150        let analyzer = QueryAnalyzer::new();
1151
1152        let pattern = TriplePattern {
1153            subject: Term::Variable(Variable::new("s").unwrap()),
1154            predicate: Term::Iri(NamedNode::new("http://example.org/predicate").unwrap()),
1155            object: Term::Variable(Variable::new("o").unwrap()),
1156        };
1157
1158        let algebra = Algebra::Bgp(vec![pattern]);
1159        let analysis = analyzer.analyze(&algebra).unwrap();
1160
1161        assert_eq!(analysis.variables.len(), 2);
1162        assert!(analysis.variables.contains(&Variable::new("s").unwrap()));
1163        assert!(analysis.variables.contains(&Variable::new("o").unwrap()));
1164    }
1165
1166    #[test]
1167    fn test_join_variable_identification() {
1168        let analyzer = QueryAnalyzer::new();
1169
1170        let pattern1 = TriplePattern {
1171            subject: Term::Variable(Variable::new("x").unwrap()),
1172            predicate: Term::Iri(NamedNode::new_unchecked("http://example.org/p1")),
1173            object: Term::Variable(Variable::new("y").unwrap()),
1174        };
1175
1176        let pattern2 = TriplePattern {
1177            subject: Term::Variable(Variable::new("y").unwrap()),
1178            predicate: Term::Iri(NamedNode::new_unchecked("http://example.org/p2")),
1179            object: Term::Variable(Variable::new("z").unwrap()),
1180        };
1181
1182        let algebra = Algebra::Join {
1183            left: Box::new(Algebra::Bgp(vec![pattern1])),
1184            right: Box::new(Algebra::Bgp(vec![pattern2])),
1185        };
1186
1187        let analysis = analyzer.analyze(&algebra).unwrap();
1188
1189        assert!(analysis
1190            .join_variables
1191            .contains(&Variable::new("y").unwrap()));
1192        assert!(!analysis
1193            .join_variables
1194            .contains(&Variable::new("x").unwrap()));
1195        assert!(!analysis
1196            .join_variables
1197            .contains(&Variable::new("z").unwrap()));
1198    }
1199
1200    #[test]
1201    fn test_filter_safety_analysis() {
1202        let analyzer = QueryAnalyzer::new();
1203
1204        let pattern = TriplePattern {
1205            subject: Term::Variable(Variable::new("s").unwrap()),
1206            predicate: Term::Iri(NamedNode::new("http://example.org/predicate").unwrap()),
1207            object: Term::Variable(Variable::new("o").unwrap()),
1208        };
1209
1210        let filter_expr = Expression::Binary {
1211            left: Box::new(Expression::Variable(Variable::new("s").unwrap())),
1212            op: BinaryOperator::Equal,
1213            right: Box::new(Expression::Literal(Literal {
1214                value: "test".to_string(),
1215                language: None,
1216                datatype: None,
1217            })),
1218        };
1219
1220        let algebra = Algebra::Filter {
1221            condition: filter_expr.clone(),
1222            pattern: Box::new(Algebra::Bgp(vec![pattern])),
1223        };
1224
1225        let analysis = analyzer.analyze(&algebra).unwrap();
1226
1227        // The filter should be safe because 's' is bound in the triple pattern
1228        assert!(analysis.filter_safety.safe_filters.contains(&filter_expr));
1229        assert!(!analysis.filter_safety.unsafe_filters.contains(&filter_expr));
1230    }
1231
1232    #[test]
1233    fn test_index_aware_analysis() {
1234        let mut analyzer = QueryAnalyzer::new();
1235
1236        // Add some index information
1237        analyzer.add_index("http://example.org/type", IndexType::Hash);
1238        analyzer.add_index("http://example.org/label", IndexType::BTree);
1239
1240        let pattern = TriplePattern {
1241            subject: Term::Variable(Variable::new("s").unwrap()),
1242            predicate: Term::Iri(NamedNode::new("http://example.org/type").unwrap()),
1243            object: Term::Variable(Variable::new("o").unwrap()),
1244        };
1245
1246        let algebra = Algebra::Bgp(vec![pattern]);
1247        let analysis = analyzer.analyze(&algebra).unwrap();
1248
1249        // Check that index optimization hints were generated
1250        assert!(!analysis.index_hints.pattern_recommendations.is_empty());
1251
1252        // Check that pattern cardinalities were estimated
1253        assert!(!analysis.pattern_cardinalities.is_empty());
1254
1255        // Check that a pattern analysis was generated for pattern 0
1256        assert!(analysis
1257            .index_hints
1258            .pattern_recommendations
1259            .contains_key(&0));
1260
1261        let pattern_analysis = &analysis.index_hints.pattern_recommendations[&0];
1262
1263        // Should have some available indexes since we added one for this predicate
1264        assert!(!pattern_analysis.available_indexes.is_empty());
1265
1266        // Should have a recommended access method
1267        assert!(pattern_analysis.recommended_access.is_some());
1268
1269        // Check that execution strategy was determined
1270        matches!(
1271            analysis.index_hints.execution_strategy,
1272            ExecutionStrategy::IndexDriven
1273                | ExecutionStrategy::HashJoin
1274                | ExecutionStrategy::SortMergeJoin
1275                | ExecutionStrategy::Adaptive
1276                | ExecutionStrategy::Parallel
1277        );
1278    }
1279
1280    #[test]
1281    fn test_join_order_optimization() {
1282        let mut analyzer = QueryAnalyzer::new();
1283
1284        // Add indexes to make some patterns more selective
1285        analyzer.add_index("http://example.org/selective", IndexType::Hash);
1286
1287        let pattern1 = TriplePattern {
1288            subject: Term::Variable(Variable::new("x").unwrap()),
1289            predicate: Term::Iri(NamedNode::new_unchecked("http://example.org/selective")),
1290            object: Term::Variable(Variable::new("y").unwrap()),
1291        };
1292
1293        let pattern2 = TriplePattern {
1294            subject: Term::Variable(Variable::new("y").unwrap()),
1295            predicate: Term::Iri(NamedNode::new_unchecked("http://example.org/expensive")),
1296            object: Term::Variable(Variable::new("z").unwrap()),
1297        };
1298
1299        let algebra = Algebra::Join {
1300            left: Box::new(Algebra::Bgp(vec![pattern1])),
1301            right: Box::new(Algebra::Bgp(vec![pattern2])),
1302        };
1303
1304        let analysis = analyzer.analyze(&algebra).unwrap();
1305
1306        // Should have join order hints
1307        assert!(!analysis.index_hints.join_order_hints.is_empty());
1308
1309        // Should have estimated costs
1310        let hint = &analysis.index_hints.join_order_hints[0];
1311        assert!(hint.estimated_cost.total_cost > 0.0);
1312    }
1313
1314    #[test]
1315    fn test_cardinality_estimation() {
1316        let analyzer = QueryAnalyzer::new();
1317
1318        // Test different pattern types for cardinality estimation
1319        let high_cardinality_pattern = TriplePattern {
1320            subject: Term::Variable(Variable::new("s").unwrap()),
1321            predicate: Term::Variable(Variable::new("p").unwrap()),
1322            object: Term::Variable(Variable::new("o").unwrap()),
1323        };
1324
1325        let medium_cardinality_pattern = TriplePattern {
1326            subject: Term::Variable(Variable::new("s").unwrap()),
1327            predicate: Term::Iri(NamedNode::new_unchecked("http://example.org/type")),
1328            object: Term::Variable(Variable::new("o").unwrap()),
1329        };
1330
1331        let low_cardinality_pattern = TriplePattern {
1332            subject: Term::Variable(Variable::new("s").unwrap()),
1333            predicate: Term::Iri(NamedNode::new_unchecked("http://example.org/type")),
1334            object: Term::Iri(NamedNode::new_unchecked("http://example.org/Person")),
1335        };
1336
1337        let high_card = analyzer.estimate_pattern_cardinality(&high_cardinality_pattern);
1338        let medium_card = analyzer.estimate_pattern_cardinality(&medium_cardinality_pattern);
1339        let low_card = analyzer.estimate_pattern_cardinality(&low_cardinality_pattern);
1340
1341        // More bound terms should result in lower cardinality estimates
1342        assert!(high_card > medium_card);
1343        assert!(medium_card > low_card);
1344    }
1345}