1#![allow(dead_code)]
14
15use crate::model::pattern::TriplePattern;
16use crate::model::{Object, Predicate, Subject, Term, Triple, Variable};
17use crate::query::algebra::TermPattern;
18use crate::query::plan::ExecutionPlan;
19use crate::OxirsError;
20use ahash::AHashMap;
21use lru::LruCache;
22use std::collections::HashMap;
23use std::num::NonZeroUsize;
24use std::sync::{Arc, Mutex, RwLock};
25use std::time::{Duration, Instant};
26
27pub struct JitCompiler {
29 compiled_cache: Arc<RwLock<CompiledQueryCache>>,
31 execution_stats: Arc<RwLock<ExecutionStatistics>>,
33 result_cache: Arc<Mutex<LruCache<QueryHash, CachedQueryResult>>>,
35 cardinality_estimates: Arc<RwLock<AHashMap<String, CardinalityEstimate>>>,
37 pattern_optimizers: Arc<PatternOptimizers>,
39 config: JitConfig,
41}
42
43#[derive(Clone)]
45struct CachedQueryResult {
46 output: QueryOutput,
47 cached_at: Instant,
48 ttl: Duration,
49}
50
51#[derive(Debug, Clone)]
53struct CardinalityEstimate {
54 estimated_count: usize,
55 confidence: f64, last_updated: Instant,
57}
58
59struct PatternOptimizers {
61 star_optimizer: StarPatternOptimizer,
63 chain_optimizer: ChainPatternOptimizer,
65 path_optimizer: PathPatternOptimizer,
67}
68
69struct StarPatternOptimizer;
71
72struct ChainPatternOptimizer;
74
75struct PathPatternOptimizer;
77
78#[derive(Debug, Clone)]
80pub struct JitConfig {
81 pub compilation_threshold: usize,
83 pub max_cache_size: usize,
85 pub aggressive_opts: bool,
87 pub target_features: TargetFeatures,
89 pub enable_result_cache: bool,
91 pub result_cache_size: usize,
93 pub result_cache_ttl: Duration,
95 pub enable_adaptive_optimization: bool,
97 pub enable_pattern_optimizers: bool,
99}
100
101impl Default for JitConfig {
102 fn default() -> Self {
103 Self {
104 compilation_threshold: 10,
105 max_cache_size: 100 * 1024 * 1024, aggressive_opts: true,
107 target_features: TargetFeatures::default(),
108 enable_result_cache: true,
109 result_cache_size: 1000,
110 result_cache_ttl: Duration::from_secs(300), enable_adaptive_optimization: true,
112 enable_pattern_optimizers: true,
113 }
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct TargetFeatures {
120 pub avx2: bool,
122 pub avx512: bool,
124 pub bmi2: bool,
126 pub vectorize: bool,
128}
129
130impl Default for TargetFeatures {
131 fn default() -> Self {
132 Self {
133 avx2: cfg!(target_feature = "avx2"),
134 avx512: cfg!(target_feature = "avx512f"),
135 bmi2: cfg!(target_feature = "bmi2"),
136 vectorize: true,
137 }
138 }
139}
140
141struct CompiledQueryCache {
143 queries: HashMap<QueryHash, CompiledQuery>,
145 total_size: usize,
147 access_order: Vec<QueryHash>,
149}
150
151struct CompiledQuery {
153 function: QueryFunction,
155 code_size: usize,
157 compile_time: Duration,
159 last_accessed: Instant,
161 execution_count: usize,
163}
164
165type QueryHash = u64;
167
168type QueryFunction = Arc<dyn Fn(&QueryContext) -> Result<QueryOutput, OxirsError> + Send + Sync>;
170
171pub struct QueryContext {
173 pub data: Arc<GraphData>,
175 pub bindings: HashMap<Variable, Term>,
177 pub limits: ExecutionLimits,
179}
180
181pub struct GraphData {
183 pub triples: Vec<Triple>,
185 pub indexes: QueryIndexes,
187}
188
189pub struct QueryIndexes {
191 pub by_subject: HashMap<Subject, Vec<usize>>,
193 pub by_predicate: HashMap<Predicate, Vec<usize>>,
195 pub by_object: HashMap<Object, Vec<usize>>,
197}
198
199#[derive(Debug, Clone)]
201pub struct ExecutionLimits {
202 pub max_results: usize,
204 pub timeout: Duration,
206 pub memory_limit: usize,
208}
209
210#[derive(Clone)]
212pub struct QueryOutput {
213 pub bindings: Vec<HashMap<Variable, Term>>,
215 pub stats: QueryStats,
217}
218
219#[derive(Debug, Clone)]
221pub struct QueryStats {
222 pub triples_scanned: usize,
224 pub results_count: usize,
226 pub execution_time: Duration,
228 pub memory_used: usize,
230}
231
232struct ExecutionStatistics {
234 query_counts: HashMap<QueryHash, usize>,
236 query_times: HashMap<QueryHash, Vec<Duration>>,
238 hot_threshold: usize,
240}
241
242impl Default for JitCompiler {
243 fn default() -> Self {
244 Self::with_config(JitConfig::default())
245 }
246}
247
248impl JitCompiler {
249 pub fn new() -> Self {
251 Self::default()
252 }
253
254 pub fn with_config(config: JitConfig) -> Self {
256 let cache_size = NonZeroUsize::new(config.result_cache_size)
257 .unwrap_or(NonZeroUsize::new(1000).expect("constant is non-zero"));
258
259 Self {
260 compiled_cache: Arc::new(RwLock::new(CompiledQueryCache::new())),
261 execution_stats: Arc::new(RwLock::new(ExecutionStatistics::new(
262 config.compilation_threshold,
263 ))),
264 result_cache: Arc::new(Mutex::new(LruCache::new(cache_size))),
265 cardinality_estimates: Arc::new(RwLock::new(AHashMap::new())),
266 pattern_optimizers: Arc::new(PatternOptimizers {
267 star_optimizer: StarPatternOptimizer,
268 chain_optimizer: ChainPatternOptimizer,
269 path_optimizer: PathPatternOptimizer,
270 }),
271 config,
272 }
273 }
274
275 pub fn execute(
277 &self,
278 plan: &ExecutionPlan,
279 context: QueryContext,
280 ) -> Result<QueryOutput, OxirsError> {
281 let hash = self.hash_plan(plan);
282
283 if self.config.enable_result_cache {
285 if let Some(cached) = self.get_cached_result(hash) {
286 return Ok(cached.output.clone());
287 }
288 }
289
290 if let Some(compiled) = self.get_compiled(hash) {
292 let result = (compiled)(&context)?;
293
294 if self.config.enable_result_cache {
296 self.cache_result(hash, result.clone());
297 }
298
299 return Ok(result);
300 }
301
302 let optimized_plan = if self.config.enable_pattern_optimizers {
304 self.optimize_pattern(plan)?
305 } else {
306 plan.clone()
307 };
308
309 let start = Instant::now();
311 let result = self.execute_interpreted(&optimized_plan, &context)?;
312 let execution_time = start.elapsed();
313
314 self.update_stats(hash, execution_time);
316
317 if self.config.enable_adaptive_optimization {
319 self.update_cardinality_estimates(plan, &result);
320 }
321
322 if self.should_compile(hash) {
324 self.compile_plan(&optimized_plan, hash)?;
325 }
326
327 if self.config.enable_result_cache {
329 self.cache_result(hash, result.clone());
330 }
331
332 Ok(result)
333 }
334
335 fn get_cached_result(&self, hash: QueryHash) -> Option<CachedQueryResult> {
337 let mut cache = self.result_cache.lock().ok()?;
338 let cached = cache.get(&hash)?;
339
340 if cached.cached_at.elapsed() > cached.ttl {
342 cache.pop(&hash);
343 return None;
344 }
345
346 Some(cached.clone())
347 }
348
349 fn cache_result(&self, hash: QueryHash, output: QueryOutput) {
351 if let Ok(mut cache) = self.result_cache.lock() {
352 cache.put(
353 hash,
354 CachedQueryResult {
355 output,
356 cached_at: Instant::now(),
357 ttl: self.config.result_cache_ttl,
358 },
359 );
360 }
361 }
362
363 fn optimize_pattern(&self, plan: &ExecutionPlan) -> Result<ExecutionPlan, OxirsError> {
365 if !self.config.enable_pattern_optimizers {
366 return Ok(plan.clone());
367 }
368
369 match self.detect_query_pattern(plan) {
371 QueryPattern::StarPattern(patterns) => {
372 tracing::debug!("Detected star pattern with {} arms", patterns.len());
373 self.pattern_optimizers
374 .star_optimizer
375 .optimize(plan, &patterns)
376 }
377 QueryPattern::ChainPattern(chain_info) => {
378 tracing::debug!("Detected chain pattern with {} links", chain_info.len());
379 self.pattern_optimizers
380 .chain_optimizer
381 .optimize(plan, &chain_info)
382 }
383 QueryPattern::PathPattern(path_info) => {
384 tracing::debug!("Detected path pattern: {:?}", path_info);
385 self.pattern_optimizers
386 .path_optimizer
387 .optimize(plan, &path_info)
388 }
389 QueryPattern::SelectivePattern(selectivity) => {
390 tracing::debug!(
391 "Detected selective pattern (selectivity: {:.2})",
392 selectivity
393 );
394 self.optimize_selective_pattern(plan, selectivity)
395 }
396 QueryPattern::Complex => {
397 tracing::debug!("Complex pattern - applying general optimizations");
398 self.optimize_complex_pattern(plan)
399 }
400 QueryPattern::Simple => {
401 Ok(plan.clone())
403 }
404 }
405 }
406
407 fn detect_query_pattern(&self, plan: &ExecutionPlan) -> QueryPattern {
409 let patterns = self.extract_triple_patterns(plan);
411
412 if let Some(star_patterns) = self.detect_star_pattern(&patterns) {
414 return QueryPattern::StarPattern(star_patterns);
415 }
416
417 if let Some(chain) = self.detect_chain_pattern(&patterns) {
419 return QueryPattern::ChainPattern(chain);
420 }
421
422 if let Some(path_info) = self.detect_path_pattern(plan) {
424 return QueryPattern::PathPattern(path_info);
425 }
426
427 if let Some(selectivity) = self.calculate_selectivity(plan) {
429 if selectivity > 0.8 {
430 return QueryPattern::SelectivePattern(selectivity);
432 }
433 }
434
435 if patterns.len() > 3 {
437 QueryPattern::Complex
438 } else {
439 QueryPattern::Simple
440 }
441 }
442
443 fn extract_triple_patterns(&self, plan: &ExecutionPlan) -> Vec<TriplePattern> {
445 let _self = self;
447 let mut patterns = Vec::new();
448
449 match plan {
450 ExecutionPlan::TripleScan { pattern } => {
451 patterns.push(pattern.clone());
452 }
453 ExecutionPlan::HashJoin { left, right, .. } => {
454 patterns.extend(self.extract_triple_patterns(left));
455 patterns.extend(self.extract_triple_patterns(right));
456 }
457 ExecutionPlan::Union { left, right } => {
458 patterns.extend(self.extract_triple_patterns(left));
459 patterns.extend(self.extract_triple_patterns(right));
460 }
461 ExecutionPlan::Filter { input, .. }
462 | ExecutionPlan::Project { input, .. }
463 | ExecutionPlan::Distinct { input, .. }
464 | ExecutionPlan::Sort { input, .. }
465 | ExecutionPlan::Limit { input, .. } => {
466 patterns.extend(self.extract_triple_patterns(input));
467 }
468 }
469
470 patterns
471 }
472
473 fn detect_star_pattern(&self, patterns: &[TriplePattern]) -> Option<Vec<TriplePattern>> {
475 if patterns.len() < 2 {
476 return None;
477 }
478
479 use crate::model::pattern::SubjectPattern;
480
481 let mut subject_groups: AHashMap<String, Vec<TriplePattern>> = AHashMap::new();
483
484 for pattern in patterns {
485 let subject_key = match &pattern.subject {
486 Some(SubjectPattern::Variable(v)) => format!("var:{}", v.as_str()),
487 Some(SubjectPattern::NamedNode(n)) => format!("node:{}", n.as_str()),
488 Some(SubjectPattern::BlankNode(b)) => format!("blank:{}", b.as_str()),
489 Some(SubjectPattern::QuotedTriple(_)) => "quoted-triple".to_string(),
490 None => continue,
491 };
492
493 subject_groups
494 .entry(subject_key)
495 .or_default()
496 .push(pattern.clone());
497 }
498
499 subject_groups
501 .into_values()
502 .max_by_key(|group| group.len())
503 .filter(|group| group.len() >= 2)
504 }
505
506 fn detect_chain_pattern(&self, patterns: &[TriplePattern]) -> Option<Vec<ChainLink>> {
508 if patterns.len() < 2 {
509 return None;
510 }
511
512 use crate::model::pattern::{ObjectPattern, SubjectPattern};
513
514 let mut chain = Vec::new();
515 let mut used = vec![false; patterns.len()];
516 let mut current_idx = 0;
517
518 chain.push(ChainLink {
520 pattern: patterns[0].clone(),
521 link_variable: None,
522 });
523 used[0] = true;
524
525 while current_idx < patterns.len() {
527 let current_object = match &patterns[current_idx].object {
528 Some(ObjectPattern::Variable(v)) => Some(v.clone()),
529 _ => None,
530 };
531
532 if let Some(obj_var) = current_object {
533 if let Some((next_idx, link_var)) =
535 patterns.iter().enumerate().find_map(|(idx, p)| {
536 if used[idx] {
537 return None;
538 }
539 match &p.subject {
540 Some(SubjectPattern::Variable(v)) if v == &obj_var => {
541 Some((idx, obj_var.clone()))
542 }
543 _ => None,
544 }
545 })
546 {
547 chain.push(ChainLink {
548 pattern: patterns[next_idx].clone(),
549 link_variable: Some(link_var),
550 });
551 used[next_idx] = true;
552 current_idx = next_idx;
553 continue;
554 }
555 }
556 break;
557 }
558
559 (chain.len() >= 2).then_some(chain)
561 }
562
563 fn detect_path_pattern(&self, _plan: &ExecutionPlan) -> Option<PathInfo> {
565 None
567 }
568
569 fn calculate_selectivity(&self, plan: &ExecutionPlan) -> Option<f64> {
571 if let Ok(estimates) = self.cardinality_estimates.read() {
572 let pattern_key = format!("{:?}", plan);
573 if let Some(estimate) = estimates.get(&pattern_key) {
574 let max_count = 1_000_000; let selectivity = 1.0 - (estimate.estimated_count as f64 / max_count as f64);
578 return Some(selectivity.clamp(0.0, 1.0));
579 }
580 }
581 None
582 }
583
584 fn optimize_selective_pattern(
586 &self,
587 plan: &ExecutionPlan,
588 _selectivity: f64,
589 ) -> Result<ExecutionPlan, OxirsError> {
590 Ok(plan.clone())
593 }
594
595 fn optimize_complex_pattern(&self, plan: &ExecutionPlan) -> Result<ExecutionPlan, OxirsError> {
597 Ok(plan.clone())
600 }
601
602 fn update_cardinality_estimates(&self, _plan: &ExecutionPlan, result: &QueryOutput) {
604 if let Ok(mut estimates) = self.cardinality_estimates.write() {
605 let pattern_key = format!("{:?}", _plan);
608 estimates.insert(
609 pattern_key,
610 CardinalityEstimate {
611 estimated_count: result.bindings.len(),
612 confidence: 0.8, last_updated: Instant::now(),
614 },
615 );
616 }
617 }
618
619 fn hash_plan(&self, plan: &ExecutionPlan) -> QueryHash {
621 use std::collections::hash_map::DefaultHasher;
622 use std::hash::{Hash, Hasher};
623
624 let mut hasher = DefaultHasher::new();
625 format!("{plan:?}").hash(&mut hasher);
626 hasher.finish()
627 }
628
629 fn get_compiled(&self, hash: QueryHash) -> Option<QueryFunction> {
631 let cache = self.compiled_cache.read().ok()?;
632 cache.queries.get(&hash).map(|q| {
633 q.function.clone()
635 })
636 }
637
638 fn execute_interpreted(
640 &self,
641 plan: &ExecutionPlan,
642 context: &QueryContext,
643 ) -> Result<QueryOutput, OxirsError> {
644 match plan {
645 ExecutionPlan::TripleScan { pattern } => self.execute_triple_scan(pattern, context),
646 ExecutionPlan::HashJoin {
647 left,
648 right,
649 join_vars,
650 } => self.execute_hash_join(left, right, join_vars, context),
651 _ => Err(OxirsError::Query("Plan type not supported".to_string())),
652 }
653 }
654
655 fn execute_triple_scan(
657 &self,
658 pattern: &TriplePattern,
659 context: &QueryContext,
660 ) -> Result<QueryOutput, OxirsError> {
661 let mut results = Vec::new();
662 let mut stats = QueryStats {
663 triples_scanned: 0,
664 results_count: 0,
665 execution_time: Duration::ZERO,
666 memory_used: 0,
667 };
668
669 let start = Instant::now();
670
671 for triple in context.data.triples.iter() {
673 stats.triples_scanned += 1;
674
675 if let Some(bindings) = self.match_triple(triple, pattern, &context.bindings) {
676 results.push(bindings);
677 stats.results_count += 1;
678
679 if results.len() >= context.limits.max_results {
680 break;
681 }
682 }
683 }
684
685 stats.execution_time = start.elapsed();
686 stats.memory_used = results.len() * std::mem::size_of::<HashMap<Variable, Term>>();
687
688 Ok(QueryOutput {
689 bindings: results,
690 stats,
691 })
692 }
693
694 fn match_triple(
696 &self,
697 triple: &Triple,
698 pattern: &crate::model::pattern::TriplePattern,
699 existing: &HashMap<Variable, Term>,
700 ) -> Option<HashMap<Variable, Term>> {
701 let mut bindings = existing.clone();
702
703 if let Some(ref subject_pattern) = pattern.subject {
705 if !self.match_subject_pattern(triple.subject(), subject_pattern, &mut bindings) {
706 return None;
707 }
708 }
709
710 if let Some(ref predicate_pattern) = pattern.predicate {
712 if !self.match_predicate_pattern(triple.predicate(), predicate_pattern, &mut bindings) {
713 return None;
714 }
715 }
716
717 if let Some(ref object_pattern) = pattern.object {
719 if !self.match_object_pattern(triple.object(), object_pattern, &mut bindings) {
720 return None;
721 }
722 }
723
724 Some(bindings)
725 }
726
727 fn match_term(
729 &self,
730 term: &Term,
731 pattern: &TermPattern,
732 bindings: &mut HashMap<Variable, Term>,
733 ) -> bool {
734 match pattern {
735 TermPattern::Variable(var) => {
736 if let Some(bound) = bindings.get(var) {
737 bound == term
738 } else {
739 bindings.insert(var.clone(), term.clone());
740 true
741 }
742 }
743 TermPattern::NamedNode(n) => {
744 matches!(term, Term::NamedNode(nn) if nn == n)
745 }
746 TermPattern::Literal(l) => {
747 matches!(term, Term::Literal(lit) if lit == l)
748 }
749 TermPattern::BlankNode(b) => {
750 matches!(term, Term::BlankNode(bn) if bn == b)
751 }
752 TermPattern::QuotedTriple(_) => {
753 panic!("RDF-star quoted triples not yet supported in JIT compilation")
754 }
755 }
756 }
757
758 fn match_subject_pattern(
760 &self,
761 subject: &Subject,
762 pattern: &crate::model::pattern::SubjectPattern,
763 bindings: &mut HashMap<Variable, Term>,
764 ) -> bool {
765 use crate::model::pattern::SubjectPattern;
766 match pattern {
767 SubjectPattern::Variable(var) => {
768 let term = Term::from_subject(subject);
769 if let Some(bound_value) = bindings.get(var) {
770 bound_value == &term
771 } else {
772 bindings.insert(var.clone(), term);
773 true
774 }
775 }
776 SubjectPattern::NamedNode(n) => matches!(subject, Subject::NamedNode(nn) if nn == n),
777 SubjectPattern::BlankNode(b) => matches!(subject, Subject::BlankNode(bn) if bn == b),
778 SubjectPattern::QuotedTriple(_) => matches!(subject, Subject::QuotedTriple(_)),
779 }
780 }
781
782 fn match_predicate_pattern(
784 &self,
785 predicate: &Predicate,
786 pattern: &crate::model::pattern::PredicatePattern,
787 bindings: &mut HashMap<Variable, Term>,
788 ) -> bool {
789 use crate::model::pattern::PredicatePattern;
790 match pattern {
791 PredicatePattern::Variable(var) => {
792 let term = Term::from_predicate(predicate);
793 if let Some(bound_value) = bindings.get(var) {
794 bound_value == &term
795 } else {
796 bindings.insert(var.clone(), term);
797 true
798 }
799 }
800 PredicatePattern::NamedNode(n) => {
801 matches!(predicate, Predicate::NamedNode(nn) if nn == n)
802 }
803 }
804 }
805
806 fn match_object_pattern(
808 &self,
809 object: &Object,
810 pattern: &crate::model::pattern::ObjectPattern,
811 bindings: &mut HashMap<Variable, Term>,
812 ) -> bool {
813 use crate::model::pattern::ObjectPattern;
814 match pattern {
815 ObjectPattern::Variable(var) => {
816 let term = Term::from_object(object);
817 if let Some(bound_value) = bindings.get(var) {
818 bound_value == &term
819 } else {
820 bindings.insert(var.clone(), term);
821 true
822 }
823 }
824 ObjectPattern::NamedNode(n) => matches!(object, Object::NamedNode(nn) if nn == n),
825 ObjectPattern::BlankNode(b) => matches!(object, Object::BlankNode(bn) if bn == b),
826 ObjectPattern::Literal(l) => matches!(object, Object::Literal(lit) if lit == l),
827 ObjectPattern::QuotedTriple(_) => matches!(object, Object::QuotedTriple(_)),
828 }
829 }
830
831 fn execute_hash_join(
833 &self,
834 left: &ExecutionPlan,
835 right: &ExecutionPlan,
836 join_vars: &[Variable],
837 context: &QueryContext,
838 ) -> Result<QueryOutput, OxirsError> {
839 let left_output = self.execute_interpreted(left, context)?;
841
842 let mut hash_table: HashMap<Vec<Term>, Vec<HashMap<Variable, Term>>> = HashMap::new();
844
845 for binding in left_output.bindings {
846 let key: Vec<Term> = join_vars
847 .iter()
848 .filter_map(|var| binding.get(var).cloned())
849 .collect();
850 hash_table.entry(key).or_default().push(binding);
851 }
852
853 let right_output = self.execute_interpreted(right, context)?;
855 let mut results = Vec::new();
856
857 for right_binding in right_output.bindings {
858 let key: Vec<Term> = join_vars
859 .iter()
860 .filter_map(|var| right_binding.get(var).cloned())
861 .collect();
862
863 if let Some(left_bindings) = hash_table.get(&key) {
864 for left_binding in left_bindings {
865 let mut merged = left_binding.clone();
866 merged.extend(right_binding.clone());
867 results.push(merged);
868 }
869 }
870 }
871
872 let results_count = results.len();
873 Ok(QueryOutput {
874 bindings: results,
875 stats: QueryStats {
876 triples_scanned: left_output.stats.triples_scanned
877 + right_output.stats.triples_scanned,
878 results_count,
879 execution_time: left_output.stats.execution_time
880 + right_output.stats.execution_time,
881 memory_used: left_output.stats.memory_used + right_output.stats.memory_used,
882 },
883 })
884 }
885
886 fn update_stats(&self, hash: QueryHash, execution_time: Duration) {
888 if let Ok(mut stats) = self.execution_stats.write() {
889 *stats.query_counts.entry(hash).or_insert(0) += 1;
890 stats
891 .query_times
892 .entry(hash)
893 .or_default()
894 .push(execution_time);
895 }
896 }
897
898 fn should_compile(&self, hash: QueryHash) -> bool {
900 if let Ok(stats) = self.execution_stats.read() {
901 if let Some(&count) = stats.query_counts.get(&hash) {
902 return count >= stats.hot_threshold;
903 }
904 }
905 false
906 }
907
908 fn compile_plan(&self, plan: &ExecutionPlan, hash: QueryHash) -> Result<(), OxirsError> {
910 let start = Instant::now();
911
912 let compiled = match plan {
914 ExecutionPlan::TripleScan { pattern } => self.compile_triple_scan(pattern)?,
915 ExecutionPlan::HashJoin {
916 left,
917 right,
918 join_vars,
919 } => self.compile_hash_join(left, right, join_vars)?,
920 _ => return Err(OxirsError::Query("Cannot compile plan type".to_string())),
921 };
922
923 let compile_time = start.elapsed();
924
925 if let Ok(mut cache) = self.compiled_cache.write() {
927 cache.add(
928 hash,
929 CompiledQuery {
930 function: compiled,
931 code_size: 1024, compile_time,
933 last_accessed: Instant::now(),
934 execution_count: 0,
935 },
936 );
937 }
938
939 Ok(())
940 }
941
942 fn compile_triple_scan(
944 &self,
945 pattern: &crate::model::pattern::TriplePattern,
946 ) -> Result<QueryFunction, OxirsError> {
947 let pattern = pattern.clone();
949
950 Ok(Arc::new(move |context: &QueryContext| {
951 let mut results = Vec::new();
952
953 if let Some(crate::model::pattern::PredicatePattern::NamedNode(pred)) =
955 &pattern.predicate
956 {
957 if let Some(indices) = context.data.indexes.by_predicate.get(&pred.clone().into()) {
959 for &idx in indices {
960 let triple = &context.data.triples[idx];
961 if let Some(bindings) =
963 match_triple_fast(triple, &pattern, &context.bindings)
964 {
965 results.push(bindings);
966 }
967 }
968 }
969 } else {
970 for triple in &context.data.triples {
972 if let Some(bindings) = match_triple_fast(triple, &pattern, &context.bindings) {
973 results.push(bindings);
974 }
975 }
976 }
977
978 let results_count = results.len();
979 Ok(QueryOutput {
980 bindings: results,
981 stats: QueryStats {
982 triples_scanned: context.data.triples.len(),
983 results_count,
984 execution_time: Duration::ZERO,
985 memory_used: 0,
986 },
987 })
988 }))
989 }
990
991 fn compile_hash_join(
993 &self,
994 _left: &ExecutionPlan,
995 _right: &ExecutionPlan,
996 _join_vars: &[Variable],
997 ) -> Result<QueryFunction, OxirsError> {
998 Ok(Arc::new(move |_context: &QueryContext| {
1000 Ok(QueryOutput {
1001 bindings: Vec::new(),
1002 stats: QueryStats {
1003 triples_scanned: 0,
1004 results_count: 0,
1005 execution_time: Duration::ZERO,
1006 memory_used: 0,
1007 },
1008 })
1009 }))
1010 }
1011}
1012
1013fn match_triple_fast(
1015 triple: &Triple,
1016 pattern: &crate::model::pattern::TriplePattern,
1017 bindings: &HashMap<Variable, Term>,
1018) -> Option<HashMap<Variable, Term>> {
1019 let mut result = bindings.clone();
1020
1021 if let Some(ref subject_pattern) = pattern.subject {
1023 use crate::model::pattern::SubjectPattern;
1024 match subject_pattern {
1025 SubjectPattern::Variable(v) => {
1026 if let Some(bound) = bindings.get(v) {
1027 if bound != &Term::from_subject(triple.subject()) {
1028 return None;
1029 }
1030 } else {
1031 result.insert(v.clone(), Term::from_subject(triple.subject()));
1032 }
1033 }
1034 SubjectPattern::NamedNode(n) => {
1035 if let Subject::NamedNode(nn) = triple.subject() {
1036 if nn != n {
1037 return None;
1038 }
1039 } else {
1040 return None;
1041 }
1042 }
1043 SubjectPattern::BlankNode(b) => {
1044 if let Subject::BlankNode(bn) = triple.subject() {
1045 if bn != b {
1046 return None;
1047 }
1048 } else {
1049 return None;
1050 }
1051 }
1052 SubjectPattern::QuotedTriple(_) => {
1053 if !matches!(triple.subject(), Subject::QuotedTriple(_)) {
1055 return None;
1056 }
1057 }
1058 }
1059 }
1060
1061 Some(result)
1064}
1065
1066impl CompiledQueryCache {
1067 fn new() -> Self {
1068 Self {
1069 queries: HashMap::new(),
1070 total_size: 0,
1071 access_order: Vec::new(),
1072 }
1073 }
1074
1075 fn add(&mut self, hash: QueryHash, query: CompiledQuery) {
1076 self.total_size += query.code_size;
1077 self.queries.insert(hash, query);
1078 self.access_order.push(hash);
1079
1080 while self.total_size > 100 * 1024 * 1024 {
1082 if let Some(oldest) = self.access_order.first() {
1084 if let Some(removed) = self.queries.remove(oldest) {
1085 self.total_size -= removed.code_size;
1086 }
1087 self.access_order.remove(0);
1088 } else {
1089 break;
1090 }
1091 }
1092 }
1093}
1094
1095impl ExecutionStatistics {
1096 fn new(hot_threshold: usize) -> Self {
1097 Self {
1098 query_counts: HashMap::new(),
1099 query_times: HashMap::new(),
1100 hot_threshold,
1101 }
1102 }
1103}
1104
1105pub mod codegen {
1107 use super::*;
1108
1109 pub struct LlvmCodeGen {
1111 target: TargetConfig,
1113 }
1114
1115 pub struct TargetConfig {
1117 pub arch: String,
1119 pub features: String,
1121 pub opt_level: OptLevel,
1123 }
1124
1125 pub enum OptLevel {
1127 None,
1128 Less,
1129 Default,
1130 Aggressive,
1131 }
1132
1133 impl LlvmCodeGen {
1134 pub fn gen_triple_scan(&self, _pattern: &TriplePattern) -> Vec<u8> {
1136 vec![0x90] }
1139
1140 pub fn gen_vector_compare(&self) -> Vec<u8> {
1142 vec![0x90] }
1145 }
1146}
1147
1148#[derive(Debug, Clone)]
1150enum QueryPattern {
1151 StarPattern(Vec<TriplePattern>),
1153 ChainPattern(Vec<ChainLink>),
1155 PathPattern(PathInfo),
1157 SelectivePattern(f64),
1159 Complex,
1161 Simple,
1163}
1164
1165#[derive(Debug, Clone)]
1167struct ChainLink {
1168 pattern: TriplePattern,
1169 link_variable: Option<Variable>, }
1171
1172#[derive(Debug, Clone)]
1174struct PathInfo {
1175 start: Variable,
1176 end: Variable,
1177 property: Variable,
1178 min_length: usize,
1179 max_length: Option<usize>,
1180}
1181
1182impl StarPatternOptimizer {
1184 fn optimize(
1186 &self,
1187 plan: &ExecutionPlan,
1188 _patterns: &[TriplePattern],
1189 ) -> Result<ExecutionPlan, OxirsError> {
1190 Ok(plan.clone())
1194 }
1195}
1196
1197impl ChainPatternOptimizer {
1198 fn optimize(
1200 &self,
1201 plan: &ExecutionPlan,
1202 _chain: &[ChainLink],
1203 ) -> Result<ExecutionPlan, OxirsError> {
1204 Ok(plan.clone())
1207 }
1208}
1209
1210impl PathPatternOptimizer {
1211 fn optimize(
1213 &self,
1214 plan: &ExecutionPlan,
1215 _path_info: &PathInfo,
1216 ) -> Result<ExecutionPlan, OxirsError> {
1217 Ok(plan.clone())
1220 }
1221}
1222
1223#[cfg(test)]
1224mod tests {
1225 use super::*;
1226
1227 #[test]
1228 fn test_jit_compiler_creation() {
1229 let compiler = JitCompiler::new();
1230
1231 let stats = compiler
1232 .execution_stats
1233 .read()
1234 .expect("lock should not be poisoned");
1235 assert_eq!(stats.query_counts.len(), 0);
1236 }
1237
1238 #[test]
1239 fn test_query_hashing() {
1240 let compiler = JitCompiler::new();
1241
1242 let plan = ExecutionPlan::TripleScan {
1243 pattern: crate::model::pattern::TriplePattern::new(
1244 Some(crate::model::pattern::SubjectPattern::Variable(
1245 Variable::new("?s").expect("valid variable name"),
1246 )),
1247 Some(crate::model::pattern::PredicatePattern::Variable(
1248 Variable::new("?p").expect("valid variable name"),
1249 )),
1250 Some(crate::model::pattern::ObjectPattern::Variable(
1251 Variable::new("?o").expect("valid variable name"),
1252 )),
1253 ),
1254 };
1255
1256 let hash1 = compiler.hash_plan(&plan);
1257 let hash2 = compiler.hash_plan(&plan);
1258
1259 assert_eq!(hash1, hash2);
1260 }
1261}