1use std::collections::{HashMap, HashSet};
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant};
10
11use anyhow::{anyhow, Result};
12use tracing::{info, span, Level};
13
14use crate::algebra::{Algebra, Expression, Term, TriplePattern, Variable};
15use crate::integrated_query_planner::{
16 IntegratedExecutionPlan, IntegratedPlannerConfig, IntegratedQueryPlanner,
17};
18
19#[derive(Debug, Clone)]
21pub struct VectorOptimizerConfig {
22 pub enable_vector_optimization: bool,
24 pub similarity_threshold: f32,
26 pub max_vector_candidates: usize,
28 pub vector_cache_size: usize,
30 pub enable_hybrid_search: bool,
32 pub embedding_dimension: usize,
34 pub distance_metric: VectorDistanceMetric,
36 pub preferred_index_types: Vec<VectorIndexType>,
38 pub complexity_threshold: f64,
40}
41
42impl Default for VectorOptimizerConfig {
43 fn default() -> Self {
44 Self {
45 enable_vector_optimization: true,
46 similarity_threshold: 0.8,
47 max_vector_candidates: 1000,
48 vector_cache_size: 10_000,
49 enable_hybrid_search: true,
50 embedding_dimension: 768, distance_metric: VectorDistanceMetric::Cosine,
52 preferred_index_types: vec![
53 VectorIndexType::Hnsw,
54 VectorIndexType::IvfFlat,
55 VectorIndexType::IvfPq,
56 ],
57 complexity_threshold: 10.0,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum VectorDistanceMetric {
65 Cosine,
66 Euclidean,
67 DotProduct,
68 Manhattan,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum VectorIndexType {
74 Hnsw,
75 IvfFlat,
76 IvfPq,
77 FlatIndex,
78 Lsh,
79}
80
81#[derive(Debug, Clone)]
83pub enum VectorSearchStrategy {
84 PureVector {
86 query_vector: Vec<f32>,
87 similarity_threshold: f32,
88 k: usize,
89 },
90 Hybrid {
92 text_query: String,
93 query_vector: Option<Vec<f32>>,
94 text_weight: f32,
95 vector_weight: f32,
96 },
97 VectorConstrained {
99 sparql_patterns: Vec<TriplePattern>,
100 vector_filter: VectorFilter,
101 },
102 SemanticExpansion {
104 original_terms: Vec<Term>,
105 expansion_candidates: Vec<(Term, f32)>,
106 max_expansions: usize,
107 },
108}
109
110#[derive(Debug, Clone)]
112pub struct VectorFilter {
113 pub subject_vector: Option<Vec<f32>>,
114 pub object_vector: Option<Vec<f32>>,
115 pub predicate_vector: Option<Vec<f32>>,
116 pub similarity_threshold: f32,
117 pub max_matches: usize,
118}
119
120pub struct VectorQueryOptimizer {
122 config: VectorOptimizerConfig,
123 integrated_planner: IntegratedQueryPlanner,
124 vector_indexes: Arc<Mutex<HashMap<String, VectorIndexInfo>>>,
125 #[allow(dead_code)]
126 embedding_cache: Arc<Mutex<HashMap<String, Vec<f32>>>>,
127 #[allow(dead_code)]
128 #[allow(clippy::type_complexity)]
129 semantic_cache: Arc<Mutex<HashMap<String, Vec<(String, f32)>>>>,
130 #[allow(dead_code)]
131 query_patterns: Arc<Mutex<HashMap<u64, VectorSearchStrategy>>>,
132 performance_metrics: Arc<Mutex<VectorPerformanceMetrics>>,
133}
134
135#[derive(Debug, Clone)]
137pub struct VectorIndexInfo {
138 pub index_type: VectorIndexType,
139 pub dimension: usize,
140 pub size: usize,
141 pub distance_metric: VectorDistanceMetric,
142 pub build_time: Duration,
143 pub last_updated: Instant,
144 pub accuracy_stats: IndexAccuracyStats,
145 pub performance_stats: IndexPerformanceStats,
146}
147
148#[derive(Debug, Clone, Default)]
150pub struct IndexAccuracyStats {
151 pub recall_at_k: HashMap<usize, f32>,
152 pub precision_at_k: HashMap<usize, f32>,
153 pub average_distance_error: f32,
154 pub query_count: usize,
155}
156
157#[derive(Debug, Clone, Default)]
159pub struct IndexPerformanceStats {
160 pub average_query_time: Duration,
161 pub queries_per_second: f32,
162 pub memory_usage: usize,
163 pub cache_hit_rate: f32,
164 pub index_efficiency: f32,
165}
166
167#[derive(Debug, Clone, Default)]
169pub struct VectorPerformanceMetrics {
170 pub vector_queries_optimized: usize,
171 pub hybrid_queries_optimized: usize,
172 pub semantic_expansions_performed: usize,
173 pub average_optimization_speedup: f32,
174 pub vector_cache_hit_rate: f32,
175 pub embedding_generation_time: Duration,
176 pub total_optimization_time: Duration,
177}
178
179#[derive(Debug, Clone)]
181pub struct VectorEnhancedPlan {
182 pub base_plan: IntegratedExecutionPlan,
184 pub vector_strategy: Option<VectorSearchStrategy>,
186 pub recommended_vector_index: Option<String>,
188 pub vector_performance_estimate: VectorPerformanceEstimate,
190 pub hybrid_config: Option<HybridSearchConfig>,
192}
193
194#[derive(Debug, Clone, Default)]
196pub struct VectorPerformanceEstimate {
197 pub estimated_query_time: Duration,
198 pub estimated_recall: f32,
199 pub estimated_memory_usage: usize,
200 pub confidence: f32,
201}
202
203#[derive(Debug, Clone)]
205pub struct HybridSearchConfig {
206 pub text_weight: f32,
207 pub vector_weight: f32,
208 pub reranking_k: usize,
209 pub fusion_method: ResultFusionMethod,
210}
211
212#[derive(Debug, Clone, Copy)]
214pub enum ResultFusionMethod {
215 LinearCombination,
216 RankFusion,
217 BayesianFusion,
218 LearningToRank,
219}
220
221impl VectorQueryOptimizer {
222 pub fn new(
224 vector_config: VectorOptimizerConfig,
225 planner_config: IntegratedPlannerConfig,
226 ) -> Result<Self> {
227 let integrated_planner = IntegratedQueryPlanner::new(planner_config)?;
228
229 Ok(Self {
230 config: vector_config,
231 integrated_planner,
232 vector_indexes: Arc::new(Mutex::new(HashMap::new())),
233 embedding_cache: Arc::new(Mutex::new(HashMap::new())),
234 semantic_cache: Arc::new(Mutex::new(HashMap::new())),
235 query_patterns: Arc::new(Mutex::new(HashMap::new())),
236 performance_metrics: Arc::new(Mutex::new(VectorPerformanceMetrics::default())),
237 })
238 }
239
240 pub fn register_vector_index(&self, name: String, index_info: VectorIndexInfo) -> Result<()> {
242 let mut indexes = self.vector_indexes.lock().expect("lock poisoned");
243 let size = index_info.size;
244 indexes.insert(name.clone(), index_info);
245
246 info!("Registered vector index: {} with {} vectors", name, size);
247 Ok(())
248 }
249
250 pub fn create_vector_enhanced_plan(&mut self, algebra: &Algebra) -> Result<VectorEnhancedPlan> {
252 let span = span!(Level::DEBUG, "vector_enhanced_planning");
253 let _enter = span.enter();
254
255 let base_plan = self.integrated_planner.create_plan(algebra)?;
257
258 let vector_opportunities = self.analyze_vector_opportunities(algebra)?;
260
261 if vector_opportunities.is_empty() {
262 return Ok(VectorEnhancedPlan {
264 base_plan,
265 vector_strategy: None,
266 recommended_vector_index: None,
267 vector_performance_estimate: VectorPerformanceEstimate::default(),
268 hybrid_config: None,
269 });
270 }
271
272 let vector_strategy = self.select_vector_strategy(&vector_opportunities, algebra)?;
274
275 let recommended_vector_index = self.select_vector_index(&vector_strategy)?;
277
278 let vector_performance_estimate =
280 self.estimate_vector_performance(&vector_strategy, &recommended_vector_index)?;
281
282 let hybrid_config = self.configure_hybrid_search(&vector_strategy)?;
284
285 self.update_optimization_metrics(&vector_strategy);
287
288 Ok(VectorEnhancedPlan {
289 base_plan,
290 vector_strategy: Some(vector_strategy),
291 recommended_vector_index,
292 vector_performance_estimate,
293 hybrid_config,
294 })
295 }
296
297 fn analyze_vector_opportunities(&self, algebra: &Algebra) -> Result<Vec<VectorOpportunity>> {
299 let mut opportunities = Vec::new();
300
301 match algebra {
302 Algebra::Bgp(patterns) => {
303 opportunities.extend(self.analyze_bgp_patterns(patterns)?);
304 }
305 Algebra::Filter { pattern, condition } => {
306 if self.is_semantic_filter(condition) {
308 opportunities.push(VectorOpportunity::SemanticFilter {
309 condition: condition.clone(),
310 estimated_selectivity: 0.1, });
312 }
313 opportunities.extend(self.analyze_vector_opportunities(pattern)?);
314 }
315 Algebra::Join { left, right } => {
316 opportunities.extend(self.analyze_vector_opportunities(left)?);
317 opportunities.extend(self.analyze_vector_opportunities(right)?);
318
319 if let Some(join_opportunity) = self.analyze_join_opportunity(left, right)? {
321 opportunities.push(join_opportunity);
322 }
323 }
324 Algebra::Union { left, right } => {
325 opportunities.extend(self.analyze_vector_opportunities(left)?);
326 opportunities.extend(self.analyze_vector_opportunities(right)?);
327 }
328 Algebra::LeftJoin {
329 left,
330 right,
331 filter: _,
332 } => {
333 opportunities.extend(self.analyze_vector_opportunities(left)?);
334 opportunities.extend(self.analyze_vector_opportunities(right)?);
335 }
336 _ => {
337 if let Some(subpattern) = self.extract_subpattern(algebra) {
339 opportunities.extend(self.analyze_vector_opportunities(&subpattern)?);
340 }
341 }
342 }
343
344 Ok(opportunities)
345 }
346
347 fn analyze_bgp_patterns(&self, patterns: &[TriplePattern]) -> Result<Vec<VectorOpportunity>> {
349 let mut opportunities = Vec::new();
350
351 for pattern in patterns {
352 if self.is_text_matching_pattern(pattern) {
354 opportunities.push(VectorOpportunity::TextSimilarity {
355 pattern: pattern.clone(),
356 estimated_matches: 100, });
358 }
359
360 if self.is_entity_similarity_pattern(pattern) {
362 opportunities.push(VectorOpportunity::EntitySimilarity {
363 pattern: pattern.clone(),
364 similarity_type: EntitySimilarityType::Conceptual,
365 });
366 }
367
368 if self.is_expandable_property_pattern(pattern) {
370 opportunities.push(VectorOpportunity::PropertyExpansion {
371 pattern: pattern.clone(),
372 expansion_depth: 2,
373 });
374 }
375 }
376
377 Ok(opportunities)
378 }
379
380 fn is_text_matching_pattern(&self, pattern: &TriplePattern) -> bool {
382 match &pattern.object {
384 Term::Literal(literal) => {
385 literal.value.len() > 5 && literal.value.chars().any(|c| c.is_alphabetic())
387 }
388 _ => false,
389 }
390 }
391
392 fn is_entity_similarity_pattern(&self, pattern: &TriplePattern) -> bool {
394 match &pattern.predicate {
396 Term::Iri(iri) => {
397 iri.as_str().contains("similar")
399 || iri.as_str().contains("related")
400 || iri.as_str().contains("type")
401 || iri.as_str().contains("category")
402 }
403 _ => false,
404 }
405 }
406
407 fn is_expandable_property_pattern(&self, pattern: &TriplePattern) -> bool {
409 match &pattern.predicate {
411 Term::Variable(_) => true, Term::Iri(iri) => {
413 let expandable_predicates = [
415 "type",
416 "category",
417 "topic",
418 "subject",
419 "theme",
420 "describes",
421 "about",
422 "concerns",
423 "deals_with",
424 ];
425
426 expandable_predicates
427 .iter()
428 .any(|pred| iri.as_str().contains(pred))
429 }
430 _ => false,
431 }
432 }
433
434 fn is_semantic_filter(&self, expression: &Expression) -> bool {
436 match expression {
438 Expression::Function { name, .. } => {
439 name.as_str().contains("similarity")
440 || name.as_str().contains("match")
441 || name.as_str().contains("distance")
442 || name.as_str().contains("semantic")
443 }
444 _ => false,
445 }
446 }
447
448 fn analyze_join_opportunity(
450 &self,
451 left: &Algebra,
452 right: &Algebra,
453 ) -> Result<Option<VectorOpportunity>> {
454 let left_vars = self.extract_variables(left);
456 let right_vars = self.extract_variables(right);
457 let shared_vars: Vec<_> = left_vars.intersection(&right_vars).collect();
458
459 if !shared_vars.is_empty() {
460 for var in shared_vars {
462 if self.is_vector_suitable_variable(var, left)
463 || self.is_vector_suitable_variable(var, right)
464 {
465 return Ok(Some(VectorOpportunity::VectorJoin {
466 left_pattern: Box::new(left.clone()),
467 right_pattern: Box::new(right.clone()),
468 join_variable: var.clone(),
469 estimated_selectivity: 0.2,
470 }));
471 }
472 }
473 }
474
475 Ok(None)
476 }
477
478 fn extract_variables(&self, algebra: &Algebra) -> HashSet<Variable> {
480 let mut vars = HashSet::new();
481
482 match algebra {
483 Algebra::Bgp(patterns) => {
484 for pattern in patterns {
485 if let Term::Variable(var) = &pattern.subject {
486 vars.insert(var.clone());
487 }
488 if let Term::Variable(var) = &pattern.predicate {
489 vars.insert(var.clone());
490 }
491 if let Term::Variable(var) = &pattern.object {
492 vars.insert(var.clone());
493 }
494 }
495 }
496 _ => {
497 }
500 }
501
502 vars
503 }
504
505 fn is_vector_suitable_variable(&self, _var: &Variable, _context: &Algebra) -> bool {
507 true }
511
512 fn extract_subpattern(&self, algebra: &Algebra) -> Option<Algebra> {
514 match algebra {
515 Algebra::Project { pattern, .. } => Some((**pattern).clone()),
516 Algebra::Distinct { pattern } => Some((**pattern).clone()),
517 Algebra::Reduced { pattern } => Some((**pattern).clone()),
518 Algebra::OrderBy { pattern, .. } => Some((**pattern).clone()),
519 Algebra::Slice { pattern, .. } => Some((**pattern).clone()),
520 Algebra::Group { pattern, .. } => Some((**pattern).clone()),
521 Algebra::Having { pattern, .. } => Some((**pattern).clone()),
522 _ => None,
523 }
524 }
525
526 fn select_vector_strategy(
528 &self,
529 opportunities: &[VectorOpportunity],
530 _algebra: &Algebra,
531 ) -> Result<VectorSearchStrategy> {
532 if opportunities.is_empty() {
533 return Err(anyhow!("No vector opportunities available"));
534 }
535
536 let primary_opportunity = &opportunities[0];
538
539 match primary_opportunity {
540 VectorOpportunity::TextSimilarity { pattern, .. } => {
541 Ok(VectorSearchStrategy::Hybrid {
542 text_query: self.extract_text_from_pattern(pattern)?,
543 query_vector: None, text_weight: 0.6,
545 vector_weight: 0.4,
546 })
547 }
548 VectorOpportunity::EntitySimilarity { pattern, .. } => {
549 Ok(VectorSearchStrategy::SemanticExpansion {
550 original_terms: vec![pattern.subject.clone()],
551 expansion_candidates: Vec::new(), max_expansions: 10,
553 })
554 }
555 VectorOpportunity::VectorJoin { .. } => {
556 Ok(VectorSearchStrategy::VectorConstrained {
557 sparql_patterns: vec![], vector_filter: VectorFilter {
559 subject_vector: None,
560 object_vector: None,
561 predicate_vector: None,
562 similarity_threshold: self.config.similarity_threshold,
563 max_matches: self.config.max_vector_candidates,
564 },
565 })
566 }
567 _ => {
568 Ok(VectorSearchStrategy::PureVector {
569 query_vector: Vec::new(), similarity_threshold: self.config.similarity_threshold,
571 k: 100,
572 })
573 }
574 }
575 }
576
577 fn extract_text_from_pattern(&self, pattern: &TriplePattern) -> Result<String> {
579 match &pattern.object {
580 Term::Literal(literal) => Ok(literal.value.clone()),
581 Term::Iri(iri) => {
582 let iri_str = iri.as_str();
584 if let Some(fragment) = iri_str.split('#').next_back() {
585 Ok(fragment.to_string())
586 } else if let Some(local) = iri_str.split('/').next_back() {
587 Ok(local.to_string())
588 } else {
589 Ok(iri_str.to_string())
590 }
591 }
592 _ => Err(anyhow!("Cannot extract text from pattern")),
593 }
594 }
595
596 fn select_vector_index(&self, strategy: &VectorSearchStrategy) -> Result<Option<String>> {
598 let indexes = self.vector_indexes.lock().expect("lock poisoned");
599
600 if indexes.is_empty() {
601 return Ok(None);
602 }
603
604 let mut best_index = None;
606 let mut best_score = 0.0f32;
607
608 for (name, info) in indexes.iter() {
609 let score = self.calculate_index_score(info, strategy);
610 if score > best_score {
611 best_score = score;
612 best_index = Some(name.clone());
613 }
614 }
615
616 Ok(best_index)
617 }
618
619 fn calculate_index_score(
621 &self,
622 info: &VectorIndexInfo,
623 strategy: &VectorSearchStrategy,
624 ) -> f32 {
625 let mut score = 0.0f32;
626
627 let type_bonus = match info.index_type {
629 VectorIndexType::Hnsw => 1.0,
630 VectorIndexType::IvfPq => 0.8,
631 VectorIndexType::IvfFlat => 0.7,
632 VectorIndexType::FlatIndex => 0.5,
633 VectorIndexType::Lsh => 0.6,
634 };
635 score += type_bonus;
636
637 score += info.performance_stats.queries_per_second / 1000.0; score += info.performance_stats.cache_hit_rate;
640 score += info.performance_stats.index_efficiency;
641
642 if let Some(recall_10) = info.accuracy_stats.recall_at_k.get(&10) {
644 score += recall_10;
645 }
646
647 match strategy {
649 VectorSearchStrategy::PureVector { k, .. }
650 if *k <= 100 && matches!(info.index_type, VectorIndexType::Hnsw) => {
652 score += 0.2;
653 }
654 VectorSearchStrategy::Hybrid { .. } => {
655 score += 0.1;
657 }
658 _ => {}
659 }
660
661 score
662 }
663
664 fn estimate_vector_performance(
666 &self,
667 strategy: &VectorSearchStrategy,
668 index_name: &Option<String>,
669 ) -> Result<VectorPerformanceEstimate> {
670 let mut estimate = VectorPerformanceEstimate::default();
671
672 if let Some(name) = index_name {
673 let indexes = self.vector_indexes.lock().expect("lock poisoned");
674 if let Some(info) = indexes.get(name) {
675 estimate.estimated_query_time = info.performance_stats.average_query_time;
676 estimate.estimated_memory_usage = info.performance_stats.memory_usage;
677
678 estimate.estimated_recall = match strategy {
680 VectorSearchStrategy::PureVector { .. } => {
681 *info.accuracy_stats.recall_at_k.get(&10).unwrap_or(&0.9)
682 }
683 VectorSearchStrategy::Hybrid { .. } => {
684 info.accuracy_stats.recall_at_k.get(&10).unwrap_or(&0.9) * 1.1
686 }
687 _ => 0.8, };
689
690 estimate.confidence = 0.8; }
692 } else {
693 estimate.estimated_query_time = Duration::from_millis(100);
695 estimate.estimated_recall = 0.7;
696 estimate.estimated_memory_usage = 1024 * 1024; estimate.confidence = 0.5;
698 }
699
700 Ok(estimate)
701 }
702
703 fn configure_hybrid_search(
705 &self,
706 strategy: &VectorSearchStrategy,
707 ) -> Result<Option<HybridSearchConfig>> {
708 match strategy {
709 VectorSearchStrategy::Hybrid {
710 text_weight,
711 vector_weight,
712 ..
713 } => Ok(Some(HybridSearchConfig {
714 text_weight: *text_weight,
715 vector_weight: *vector_weight,
716 reranking_k: 100,
717 fusion_method: ResultFusionMethod::LinearCombination,
718 })),
719 _ => Ok(None),
720 }
721 }
722
723 fn update_optimization_metrics(&self, strategy: &VectorSearchStrategy) {
725 let mut metrics = self.performance_metrics.lock().expect("lock poisoned");
726
727 match strategy {
728 VectorSearchStrategy::PureVector { .. } => {
729 metrics.vector_queries_optimized += 1;
730 }
731 VectorSearchStrategy::Hybrid { .. } => {
732 metrics.hybrid_queries_optimized += 1;
733 }
734 VectorSearchStrategy::SemanticExpansion { .. } => {
735 metrics.semantic_expansions_performed += 1;
736 }
737 _ => {}
738 }
739 }
740
741 pub fn get_performance_metrics(&self) -> VectorPerformanceMetrics {
743 self.performance_metrics
744 .lock()
745 .expect("lock poisoned")
746 .clone()
747 }
748
749 pub fn update_vector_execution_feedback(
751 &mut self,
752 _strategy_hash: u64,
753 actual_duration: Duration,
754 _actual_recall: f32,
755 _actual_memory: usize,
756 success: bool,
757 ) -> Result<()> {
758 let mut metrics = self.performance_metrics.lock().expect("lock poisoned");
760
761 if success {
762 let base_time = Duration::from_millis(500); let speedup = base_time.as_millis() as f32 / actual_duration.as_millis() as f32;
765
766 let total_optimizations =
767 metrics.vector_queries_optimized + metrics.hybrid_queries_optimized;
768
769 if total_optimizations > 0 {
770 metrics.average_optimization_speedup = (metrics.average_optimization_speedup
771 * (total_optimizations - 1) as f32
772 + speedup)
773 / total_optimizations as f32;
774 }
775 }
776
777 Ok(())
778 }
779}
780
781#[derive(Debug, Clone)]
783pub enum VectorOpportunity {
784 TextSimilarity {
786 pattern: TriplePattern,
787 estimated_matches: usize,
788 },
789 EntitySimilarity {
791 pattern: TriplePattern,
792 similarity_type: EntitySimilarityType,
793 },
794 PropertyExpansion {
796 pattern: TriplePattern,
797 expansion_depth: usize,
798 },
799 SemanticFilter {
801 condition: Expression,
802 estimated_selectivity: f32,
803 },
804 VectorJoin {
806 left_pattern: Box<Algebra>,
807 right_pattern: Box<Algebra>,
808 join_variable: Variable,
809 estimated_selectivity: f32,
810 },
811}
812
813#[derive(Debug, Clone, Copy)]
815pub enum EntitySimilarityType {
816 Conceptual,
817 Taxonomic,
818 Relational,
819 Contextual,
820}
821
822#[derive(Debug, Clone)]
824pub struct VectorIndexRecommendation {
825 pub recommended_type: VectorIndexType,
826 pub estimated_benefit: f32,
827 pub creation_cost_estimate: Duration,
828 pub memory_requirement: usize,
829 pub maintenance_overhead: f32,
830}