1use 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub enum IndexType {
15 BTree,
17 Hash,
19 FullText,
21 Spatial,
23 Custom(String),
25}
26
27#[derive(Debug, Clone)]
29pub struct IndexAccess {
30 pub index_type: IndexType,
32 pub pattern_position: usize,
34 pub pattern_index: usize,
36 pub selectivity: f64,
38 pub cost_estimate: CostEstimate,
40 pub io_pattern: IOPattern,
42 pub improvement_ratio: f64,
44}
45
46#[derive(Debug, Clone)]
48pub struct PatternIndexAnalysis {
49 pub available_indexes: Vec<IndexAccess>,
51 pub recommended_access: Option<IndexAccess>,
53 pub full_scan_cardinality: usize,
55 pub indexed_cardinality: usize,
57 pub improvement_ratio: f64,
59}
60
61#[derive(Debug, Clone)]
63pub struct IndexOptimizationHints {
64 pub pattern_recommendations: HashMap<usize, PatternIndexAnalysis>,
66 pub join_order_hints: Vec<JoinOrderHint>,
68 pub filter_placement_hints: Vec<FilterPlacementHint>,
70 pub execution_strategy: ExecutionStrategy,
72}
73
74#[derive(Debug, Clone)]
76pub struct JoinOrderHint {
77 pub pattern_order: Vec<usize>,
79 pub estimated_cost: CostEstimate,
81 pub reasoning: String,
83}
84
85#[derive(Debug, Clone)]
87pub struct FilterPlacementHint {
88 pub filter: Expression,
90 pub recommended_placement: usize,
92 pub selectivity: f64,
94 pub cost_benefit: f64,
96}
97
98#[derive(Debug, Clone)]
100pub enum ExecutionStrategy {
101 Sequential,
103 Parallel,
105 IndexDriven,
107 HashJoin,
109 SortMergeJoin,
111 Adaptive,
113}
114
115#[derive(Debug, Clone)]
117pub struct FilterSafetyAnalysis {
118 pub safe_filters: Vec<Expression>,
120 pub unsafe_filters: Vec<Expression>,
122 pub filter_dependencies: Vec<(Expression, HashSet<Variable>)>,
124}
125
126#[derive(Debug, Clone)]
128pub struct QueryAnalysis {
129 pub variables: HashSet<Variable>,
131 pub projected_variables: HashSet<Variable>,
133 pub filter_variables: HashSet<Variable>,
135 pub join_variables: HashSet<Variable>,
137 pub variable_scopes: HashMap<Variable, VariableScope>,
139 pub filter_safety: FilterSafetyAnalysis,
141 pub type_consistency: TypeConsistencyAnalysis,
143 pub index_hints: IndexOptimizationHints,
145 pub pattern_cardinalities: HashMap<usize, usize>,
147 pub validation_errors: Vec<ValidationError>,
149}
150
151#[derive(Debug, Clone)]
153pub struct VariableScope {
154 pub pattern_indices: HashSet<usize>,
156 pub is_bound: bool,
158 pub in_projection: bool,
160 pub in_filters: bool,
162 pub in_group_by: bool,
164 pub in_order_by: bool,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum FilterSafety {
171 Safe,
173 UnsafeOptional,
175 UnsafeUnbound,
177 UnsafeAggregate,
179 UnsafeService,
181}
182
183#[derive(Debug, Clone)]
185pub struct TypeConsistencyAnalysis {
186 pub variable_types: HashMap<Variable, VariableType>,
188 pub type_errors: Vec<TypeError>,
190 pub type_warnings: Vec<TypeWarning>,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
196pub enum VariableType {
197 Resource,
199 Literal,
201 Numeric,
203 String,
205 Boolean,
207 DateTime,
209 Unknown,
211}
212
213#[derive(Debug, Clone)]
215pub struct TypeError {
216 pub variable: Variable,
218 pub expected: VariableType,
220 pub actual: VariableType,
222 pub location: String,
224 pub message: String,
226}
227
228#[derive(Debug, Clone)]
230pub struct TypeWarning {
231 pub variable: Variable,
233 pub message: String,
235 pub location: String,
237}
238
239#[derive(Debug, Clone)]
241pub struct ValidationError {
242 pub error_type: ValidationErrorType,
244 pub message: String,
246 pub location: String,
248 pub suggestion: Option<String>,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
254pub enum ValidationErrorType {
255 UnboundVariable,
257 TypeMismatch,
259 InvalidAggregate,
261 InvalidService,
263 CircularDependency,
265 SemanticInconsistency,
267}
268
269#[derive(Debug, Clone)]
271pub struct QueryAnalyzer {
272 #[allow(dead_code)]
274 statistics: Option<StatisticsCollector>,
275 #[allow(dead_code)]
277 cost_model: Option<CostModel>,
278 pub enable_type_inference: bool,
280}
281
282impl QueryAnalyzer {
283 pub fn new() -> Self {
285 Self {
286 statistics: None,
287 cost_model: None,
288 enable_type_inference: true,
289 }
290 }
291
292 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 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 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 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 pub fn analyze(&self, algebra: &Algebra) -> Result<QueryAnalysis> {
351 self.analyze_query(algebra)
352 }
353
354 pub fn add_index(&mut self, _predicate: &str, _index_type: IndexType) {
356 }
360
361 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 match bound_terms {
377 0 => 1_000_000, 1 => 100_000, 2 => 1_000, 3 => 1, _ => 1,
382 }
383 }
384
385 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 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 Ok(join_vars_detailed.into_keys().collect())
406 }
407
408 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 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 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 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 }
491 }
492 Ok(())
493 }
494
495 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 #[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 }
539 }
540 Ok(())
541 }
542
543 fn collect_variables_from_group_condition(
545 &self,
546 _condition: &crate::algebra::GroupCondition,
547 _variables: &mut HashSet<Variable>,
548 ) -> Result<()> {
549 Ok(())
553 }
554
555 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 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 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 _ => {} }
595 }
596
597 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 join_vars.retain(|_var, pattern_indices| pattern_indices.len() > 1);
617
618 Ok(join_vars)
619 }
620
621 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 #[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 _ => {} }
648 }
649
650 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 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 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 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 fn is_variable_bound(&self, var: &Variable, algebra: &Algebra) -> bool {
715 let patterns = self.extract_bgp_patterns(algebra);
717 patterns
718 .iter()
719 .any(|pattern| self.pattern_contains_variable(pattern, var))
720 }
721
722 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 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 fn is_in_group_by(&self, _var: &Variable, _algebra: &Algebra) -> bool {
739 false
742 }
743
744 fn is_in_order_by(&self, _var: &Variable, _algebra: &Algebra) -> bool {
746 false
749 }
750
751 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 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 _ => {} }
783 Ok(())
784 }
785
786 fn determine_filter_safety(
788 &self,
789 condition: &Expression,
790 context: &Algebra,
791 ) -> Result<FilterSafety> {
792 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 #[allow(clippy::only_used_in_recursion)]
814 fn contains_aggregate_function(&self, expr: &Expression) -> bool {
815 match expr {
816 Expression::Function { name, .. } => {
817 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 fn contains_service_call(&self, _expr: &Expression) -> bool {
833 false
836 }
837
838 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 fn in_optional_context(&self, _context: &Algebra) -> bool {
854 false
857 }
858
859 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 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 fn infer_variable_type(&self, var: &Variable, algebra: &Algebra) -> Result<VariableType> {
884 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); }
891 if matches!(&pattern.predicate, Term::Variable(v) if v == var) {
892 return Ok(VariableType::Resource); }
894 if matches!(&pattern.object, Term::Variable(v) if v == var) {
895 return Ok(VariableType::Unknown);
897 }
898 }
899
900 Ok(VariableType::Unknown)
901 }
902
903 fn detect_type_errors(
905 &self,
906 _algebra: &Algebra,
907 _variable_types: &HashMap<Variable, VariableType>,
908 _type_errors: &mut [TypeError],
909 ) -> Result<()> {
910 Ok(())
913 }
914
915 fn detect_type_warnings(
917 &self,
918 _algebra: &Algebra,
919 _variable_types: &HashMap<Variable, VariableType>,
920 _type_warnings: &mut [TypeWarning],
921 ) -> Result<()> {
922 Ok(())
925 }
926
927 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 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 if !matches!(&pattern.subject, Term::Variable(_)) {
959 available_indexes.push(IndexAccess {
961 index_type: IndexType::BTree,
962 pattern_position: 0,
963 pattern_index: pattern_idx,
964 selectivity: 0.1, 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 available_indexes.push(IndexAccess {
974 index_type: IndexType::Hash,
975 pattern_position: 1,
976 pattern_index: pattern_idx,
977 selectivity: 0.05, 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, indexed_cardinality: 1000, improvement_ratio: 1000.0, })
1001 }
1002
1003 fn generate_join_order_hints(&self, patterns: &[TriplePattern]) -> Result<Vec<JoinOrderHint>> {
1005 let mut hints = Vec::new();
1006
1007 if patterns.len() > 1 {
1008 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 fn generate_filter_placement_hints(
1022 &self,
1023 _algebra: &Algebra,
1024 ) -> Result<Vec<FilterPlacementHint>> {
1025 let hints = Vec::new();
1026 Ok(hints)
1029 }
1030
1031 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 #[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 pub fn validate_semantics(&self, algebra: &Algebra) -> Result<Vec<ValidationError>> {
1064 let mut errors = Vec::new();
1065
1066 self.check_unbound_variables_in_projection(algebra, &mut errors)?;
1068
1069 self.check_invalid_aggregates(algebra, &mut errors)?;
1071
1072 self.check_semantic_consistency(algebra, &mut errors)?;
1074
1075 Ok(errors)
1076 }
1077
1078 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 fn check_invalid_aggregates(
1109 &self,
1110 _algebra: &Algebra,
1111 _errors: &mut [ValidationError],
1112 ) -> Result<()> {
1113 Ok(())
1116 }
1117
1118 fn check_semantic_consistency(
1120 &self,
1121 _algebra: &Algebra,
1122 _errors: &mut [ValidationError],
1123 ) -> Result<()> {
1124 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 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 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 assert!(!analysis.index_hints.pattern_recommendations.is_empty());
1251
1252 assert!(!analysis.pattern_cardinalities.is_empty());
1254
1255 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 assert!(!pattern_analysis.available_indexes.is_empty());
1265
1266 assert!(pattern_analysis.recommended_access.is_some());
1268
1269 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 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 assert!(!analysis.index_hints.join_order_hints.is_empty());
1308
1309 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 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 assert!(high_card > medium_card);
1343 assert!(medium_card > low_card);
1344 }
1345}