1use crate::algebra::{Solution, Term, Variable};
10use anyhow::Result;
11use std::alloc::{alloc, dealloc, Layout};
12use std::collections::HashMap;
13use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
14
15pub struct LockFreeWorkStealingQueue<T> {
17 buffer: AtomicPtr<T>,
19 capacity: usize,
21 head: AtomicUsize,
23 tail: AtomicUsize,
25 mask: usize,
27}
28
29impl<T> LockFreeWorkStealingQueue<T> {
30 pub fn new(capacity: usize) -> Self {
32 let capacity = capacity.next_power_of_two();
34 let mask = capacity - 1;
35
36 let layout = Layout::array::<T>(capacity).expect("Invalid layout");
37 let buffer = unsafe { alloc(layout) as *mut T };
38
39 Self {
40 buffer: AtomicPtr::new(buffer),
41 capacity,
42 head: AtomicUsize::new(0),
43 tail: AtomicUsize::new(0),
44 mask,
45 }
46 }
47
48 pub fn push(&self, item: T) -> Result<()> {
50 let tail = self.tail.load(Ordering::Relaxed);
51 let head = self.head.load(Ordering::Acquire);
52
53 if tail - head >= self.capacity {
55 return Err(anyhow::anyhow!("Work queue is full"));
56 }
57
58 unsafe {
59 let buffer = self.buffer.load(Ordering::Relaxed);
60 let index = tail & self.mask;
61 std::ptr::write(buffer.add(index), item);
62 }
63
64 self.tail.store(tail + 1, Ordering::Release);
65 Ok(())
66 }
67
68 pub fn pop(&self) -> Option<T> {
70 let tail = self.tail.load(Ordering::Relaxed);
71 if tail == 0 {
72 return None;
73 }
74
75 let new_tail = tail - 1;
76 self.tail.store(new_tail, Ordering::Relaxed);
77
78 let head = self.head.load(Ordering::Acquire);
79 if new_tail > head {
80 unsafe {
82 let buffer = self.buffer.load(Ordering::Relaxed);
83 let index = new_tail & self.mask;
84 Some(std::ptr::read(buffer.add(index)))
85 }
86 } else if new_tail == head {
87 if self
89 .head
90 .compare_exchange_weak(head, head + 1, Ordering::SeqCst, Ordering::Relaxed)
91 .is_ok()
92 {
93 unsafe {
94 let buffer = self.buffer.load(Ordering::Relaxed);
95 let index = head & self.mask;
96 Some(std::ptr::read(buffer.add(index)))
97 }
98 } else {
99 self.tail.store(tail, Ordering::Relaxed);
101 None
102 }
103 } else {
104 self.tail.store(tail, Ordering::Relaxed);
106 None
107 }
108 }
109
110 pub fn steal(&self) -> Option<T> {
112 let head = self.head.load(Ordering::Acquire);
113 let tail = self.tail.load(Ordering::Acquire);
114
115 if head >= tail {
116 return None;
117 }
118
119 unsafe {
120 let buffer = self.buffer.load(Ordering::Relaxed);
121 let index = head & self.mask;
122 let item = std::ptr::read(buffer.add(index));
123
124 if self
125 .head
126 .compare_exchange_weak(head, head + 1, Ordering::SeqCst, Ordering::Relaxed)
127 .is_ok()
128 {
129 Some(item)
130 } else {
131 std::mem::forget(item); None
134 }
135 }
136 }
137
138 pub fn is_empty(&self) -> bool {
140 let head = self.head.load(Ordering::Acquire);
141 let tail = self.tail.load(Ordering::Acquire);
142 head >= tail
143 }
144
145 pub fn len(&self) -> usize {
147 let head = self.head.load(Ordering::Relaxed);
148 let tail = self.tail.load(Ordering::Relaxed);
149 tail.saturating_sub(head)
150 }
151}
152
153impl<T> Drop for LockFreeWorkStealingQueue<T> {
154 fn drop(&mut self) {
155 while self.pop().is_some() {}
157
158 let buffer = self.buffer.load(Ordering::Relaxed);
160 if !buffer.is_null() {
161 unsafe {
162 let layout = Layout::array::<T>(self.capacity).expect("Invalid layout");
163 dealloc(buffer as *mut u8, layout);
164 }
165 }
166 }
167}
168
169pub struct MemoryPool<T> {
171 available: LockFreeWorkStealingQueue<Box<T>>,
173 factory: fn() -> T,
175 max_size: usize,
177 current_size: AtomicUsize,
179}
180
181impl<T> MemoryPool<T> {
182 pub fn new(initial_size: usize, max_size: usize, factory: fn() -> T) -> Self {
184 let pool = Self {
185 available: LockFreeWorkStealingQueue::new(max_size),
186 factory,
187 max_size,
188 current_size: AtomicUsize::new(0),
189 };
190
191 for _ in 0..initial_size {
193 let obj = Box::new(factory());
194 let _ = pool.available.push(obj);
195 pool.current_size.store(initial_size, Ordering::Relaxed);
196 }
197
198 pool
199 }
200
201 pub fn acquire(&self) -> PooledObject<'_, T> {
203 match self.available.steal() {
204 Some(obj) => PooledObject {
205 object: Some(obj),
206 pool: self,
207 },
208 _ => {
209 let obj = Box::new((self.factory)());
211 PooledObject {
212 object: Some(obj),
213 pool: self,
214 }
215 }
216 }
217 }
218
219 fn return_object(&self, obj: Box<T>) {
221 let current = self.current_size.load(Ordering::Relaxed);
222 if current < self.max_size && self.available.push(obj).is_ok() {
223 self.current_size.fetch_add(1, Ordering::Relaxed);
224 }
225 }
228}
229
230pub struct PooledObject<'a, T> {
232 object: Option<Box<T>>,
233 pool: &'a MemoryPool<T>,
234}
235
236impl<'a, T> PooledObject<'a, T> {
237 pub fn get_mut(&mut self) -> &mut T {
239 self.object
240 .as_mut()
241 .expect("pooled object should be present")
242 }
243
244 pub fn get(&self) -> &T {
246 self.object
247 .as_ref()
248 .expect("pooled object should be present")
249 }
250}
251
252impl<'a, T> Drop for PooledObject<'a, T> {
253 fn drop(&mut self) {
254 if let Some(obj) = self.object.take() {
255 self.pool.return_object(obj);
256 }
257 }
258}
259
260pub struct CacheFriendlyHashJoin {
262 num_partitions: usize,
264 #[allow(dead_code)]
266 radix_bits: u32,
267 hash_table_pool: MemoryPool<HashMap<u64, Vec<Solution>>>,
269}
270
271impl CacheFriendlyHashJoin {
272 pub fn new(num_partitions: usize) -> Self {
274 let num_partitions = num_partitions.next_power_of_two();
275 let radix_bits = num_partitions.trailing_zeros();
276
277 Self {
278 num_partitions,
279 radix_bits,
280 hash_table_pool: MemoryPool::new(num_partitions, num_partitions * 2, || {
281 HashMap::with_capacity(1024)
282 }),
283 }
284 }
285
286 pub fn join_parallel(
288 &self,
289 left_solutions: Vec<Solution>,
290 right_solutions: Vec<Solution>,
291 join_variables: &[Variable],
292 ) -> Result<Vec<Solution>> {
293 let left_partitions = self.partition_solutions(left_solutions, join_variables)?;
295 let right_partitions = self.partition_solutions(right_solutions, join_variables)?;
296
297 let results: Vec<_> = (0..self.num_partitions)
299 .map(|i| self.join_partition(&left_partitions[i], &right_partitions[i], join_variables))
300 .collect::<Result<Vec<_>>>()?;
301
302 Ok(results.into_iter().flatten().collect())
304 }
305
306 fn partition_solutions(
308 &self,
309 solutions: Vec<Solution>,
310 join_variables: &[Variable],
311 ) -> Result<Vec<Vec<Solution>>> {
312 let mut partitions = vec![Vec::new(); self.num_partitions];
313
314 for solution in solutions {
315 let hash = self.compute_join_key_hash(&solution, join_variables);
316 let partition_id = (hash as usize) & (self.num_partitions - 1);
317 partitions[partition_id].push(solution);
318 }
319
320 Ok(partitions)
321 }
322
323 fn join_partition(
325 &self,
326 left_partition: &[Solution],
327 right_partition: &[Solution],
328 join_variables: &[Variable],
329 ) -> Result<Vec<Solution>> {
330 if left_partition.is_empty() || right_partition.is_empty() {
331 return Ok(Vec::new());
332 }
333
334 let (build_side, probe_side, build_left) = if left_partition.len() <= right_partition.len()
336 {
337 (left_partition, right_partition, true)
338 } else {
339 (right_partition, left_partition, false)
340 };
341
342 let mut hash_table = self.hash_table_pool.acquire();
344 hash_table.get_mut().clear();
345
346 for solution in build_side {
348 let key = self.compute_join_key_hash(solution, join_variables);
349 hash_table
350 .get_mut()
351 .entry(key)
352 .or_default()
353 .push(solution.clone());
354 }
355
356 let mut results = Vec::new();
358 for probe_solution in probe_side {
359 let key = self.compute_join_key_hash(probe_solution, join_variables);
360 if let Some(build_solutions) = hash_table.get().get(&key) {
361 for build_solution in build_solutions {
362 if self.solutions_join_compatible(
363 build_solution,
364 probe_solution,
365 join_variables,
366 ) {
367 let joined = if build_left {
368 self.merge_solutions(build_solution, probe_solution)?
369 } else {
370 self.merge_solutions(probe_solution, build_solution)?
371 };
372 results.push(joined);
373 }
374 }
375 }
376 }
377
378 Ok(results)
379 }
380
381 fn compute_join_key_hash(&self, solution: &Solution, join_variables: &[Variable]) -> u64 {
383 use std::collections::hash_map::DefaultHasher;
384 use std::hash::{Hash, Hasher};
385
386 let mut hasher = DefaultHasher::new();
387 for binding in solution {
388 for var in join_variables {
389 if let Some(term) = binding.get(var) {
390 term.hash(&mut hasher);
391 }
392 }
393 }
394 hasher.finish()
395 }
396
397 fn solutions_join_compatible(
399 &self,
400 left: &Solution,
401 right: &Solution,
402 join_variables: &[Variable],
403 ) -> bool {
404 for left_binding in left {
405 for right_binding in right {
406 for var in join_variables {
407 if let (Some(left_term), Some(right_term)) =
408 (left_binding.get(var), right_binding.get(var))
409 {
410 if left_term != right_term {
411 return false;
412 }
413 }
414 }
415 }
416 }
417 true
418 }
419
420 fn merge_solutions(&self, left: &Solution, right: &Solution) -> Result<Solution> {
422 let mut result = Vec::new();
423
424 for left_binding in left {
425 for right_binding in right {
426 let mut merged_binding = left_binding.clone();
427
428 for (var, term) in right_binding {
430 if !merged_binding.contains_key(var) {
431 merged_binding.insert(var.clone(), term.clone());
432 }
433 }
434
435 result.push(merged_binding);
436 }
437 }
438
439 Ok(result)
440 }
441}
442
443pub struct SIMDOptimizedOps;
445
446impl SIMDOptimizedOps {
447 #[cfg(target_feature = "sse2")]
449 pub fn bulk_string_compare(strings: &[String], pattern: &str) -> Vec<bool> {
450 use rayon::prelude::*;
452
453 strings
454 .par_chunks(256) .flat_map(|chunk| {
456 chunk
457 .iter()
458 .map(|s| s.contains(pattern))
459 .collect::<Vec<_>>()
460 })
461 .collect()
462 }
463
464 #[cfg(not(target_feature = "sse2"))]
465 pub fn bulk_string_compare(strings: &[String], pattern: &str) -> Vec<bool> {
466 use rayon::prelude::*;
467 strings.par_iter().map(|s| s.contains(pattern)).collect()
468 }
469
470 pub fn bulk_hash_compute(terms: &[Term]) -> Vec<u64> {
472 use rayon::prelude::*;
473 use std::collections::hash_map::DefaultHasher;
474 use std::hash::{Hash, Hasher};
475
476 terms
477 .par_chunks(1024) .flat_map(|chunk| {
479 chunk
480 .iter()
481 .map(|term| {
482 let mut hasher = DefaultHasher::new();
483 term.hash(&mut hasher);
484 hasher.finish()
485 })
486 .collect::<Vec<_>>()
487 })
488 .collect()
489 }
490
491 pub fn parallel_count_aggregate(
493 solutions: &[Solution],
494 group_var: &Variable,
495 ) -> HashMap<Term, usize> {
496 use rayon::prelude::*;
497
498 solutions
499 .par_iter()
500 .flat_map(|solution| {
501 solution
502 .par_iter()
503 .filter_map(|binding| binding.get(group_var).map(|term| (term.clone(), 1)))
504 })
505 .fold(HashMap::new, |mut acc, (term, count)| {
506 *acc.entry(term).or_insert(0) += count;
507 acc
508 })
509 .reduce(HashMap::new, |mut acc1, acc2| {
510 for (term, count) in acc2 {
511 *acc1.entry(term).or_insert(0) += count;
512 }
513 acc1
514 })
515 }
516
517 pub fn bulk_equality_check(terms1: &[Term], terms2: &[Term]) -> Vec<bool> {
519 use rayon::prelude::*;
520
521 terms1
522 .par_iter()
523 .zip(terms2.par_iter())
524 .map(|(t1, t2)| t1 == t2)
525 .collect()
526 }
527
528 pub fn bulk_numeric_sum(literals: &[crate::algebra::Literal]) -> Result<f64> {
530 use rayon::prelude::*;
531
532 literals
533 .par_iter()
534 .map(|lit| lit.value.parse::<f64>())
535 .try_fold(|| 0.0, |acc, val| val.map(|v| acc + v))
536 .try_reduce(|| 0.0, |a, b| Ok(a + b))
537 .map_err(|e| anyhow::anyhow!("Failed to parse numeric value: {}", e))
538 }
539
540 pub fn bulk_filter_solutions(
542 solutions: &[Solution],
543 predicate: fn(&Solution) -> bool,
544 ) -> Vec<Solution> {
545 use rayon::prelude::*;
546
547 solutions
548 .par_iter()
549 .filter(|solution| predicate(solution))
550 .cloned()
551 .collect()
552 }
553
554 pub fn bulk_project_solutions(solutions: &[Solution], variables: &[Variable]) -> Vec<Solution> {
556 use rayon::prelude::*;
557
558 solutions
559 .par_iter()
560 .map(|solution| {
561 solution
562 .iter()
563 .map(|binding| {
564 let mut projected_binding = HashMap::new();
565 for var in variables {
566 if let Some(term) = binding.get(var) {
567 projected_binding.insert(var.clone(), term.clone());
568 }
569 }
570 projected_binding
571 })
572 .collect()
573 })
574 .collect()
575 }
576
577 pub fn bulk_deduplicate_solutions(solutions: Vec<Solution>) -> Vec<Solution> {
579 use rayon::prelude::*;
580 use std::collections::HashSet;
581 use std::sync::Mutex;
582
583 let seen = Mutex::new(HashSet::new());
584
585 solutions
586 .into_par_iter()
587 .filter(|solution| {
588 let solution_hash = Self::compute_solution_hash(solution);
589 let mut seen_set = seen.lock().expect("lock should not be poisoned");
590 seen_set.insert(solution_hash)
591 })
592 .collect()
593 }
594
595 fn compute_solution_hash(solution: &Solution) -> u64 {
597 use std::collections::hash_map::DefaultHasher;
598 use std::hash::{Hash, Hasher};
599
600 let mut hasher = DefaultHasher::new();
601 for binding in solution {
602 let mut sorted_items: Vec<_> = binding.iter().collect();
604 sorted_items.sort_by(|a, b| a.0.cmp(b.0));
605 sorted_items.hash(&mut hasher);
606 }
607 hasher.finish()
608 }
609}
610
611pub struct SortMergeJoin {
613 #[allow(dead_code)]
615 memory_threshold: usize,
616 #[allow(dead_code)]
618 temp_dir: Option<std::path::PathBuf>,
619}
620
621impl SortMergeJoin {
622 pub fn new(memory_threshold: usize) -> Self {
624 Self {
625 memory_threshold,
626 temp_dir: None,
627 }
628 }
629
630 pub fn with_temp_dir(memory_threshold: usize, temp_dir: std::path::PathBuf) -> Self {
632 Self {
633 memory_threshold,
634 temp_dir: Some(temp_dir),
635 }
636 }
637
638 pub fn join(
640 &self,
641 left_solutions: Vec<Solution>,
642 right_solutions: Vec<Solution>,
643 join_variables: &[Variable],
644 ) -> Result<Vec<Solution>> {
645 let sorted_left = self.sort_solutions(left_solutions, join_variables)?;
647 let sorted_right = self.sort_solutions(right_solutions, join_variables)?;
648
649 self.merge_sorted_solutions(sorted_left, sorted_right, join_variables)
651 }
652
653 fn sort_solutions(
655 &self,
656 mut solutions: Vec<Solution>,
657 join_variables: &[Variable],
658 ) -> Result<Vec<Solution>> {
659 solutions.sort_by(|a, b| self.compare_solutions_by_join_key(a, b, join_variables));
660 Ok(solutions)
661 }
662
663 fn compare_solutions_by_join_key(
665 &self,
666 left: &Solution,
667 right: &Solution,
668 join_variables: &[Variable],
669 ) -> std::cmp::Ordering {
670 use std::cmp::Ordering;
671
672 let left_binding = left.first();
674 let right_binding = right.first();
675
676 match (left_binding, right_binding) {
677 (Some(l_binding), Some(r_binding)) => {
678 for var in join_variables {
679 let left_term = l_binding.get(var);
680 let right_term = r_binding.get(var);
681
682 let cmp = match (left_term, right_term) {
683 (Some(l), Some(r)) => self.compare_terms(l, r),
684 (Some(_), None) => Ordering::Greater,
685 (None, Some(_)) => Ordering::Less,
686 (None, None) => Ordering::Equal,
687 };
688
689 if cmp != Ordering::Equal {
690 return cmp;
691 }
692 }
693 Ordering::Equal
694 }
695 (Some(_), None) => Ordering::Greater,
696 (None, Some(_)) => Ordering::Less,
697 (None, None) => Ordering::Equal,
698 }
699 }
700
701 fn compare_terms(&self, left: &Term, right: &Term) -> std::cmp::Ordering {
703 use std::cmp::Ordering;
704
705 match (left, right) {
706 (Term::Literal(l), Term::Literal(r)) => {
707 if let (Ok(l_num), Ok(r_num)) = (l.value.parse::<f64>(), r.value.parse::<f64>()) {
709 l_num.partial_cmp(&r_num).unwrap_or(Ordering::Equal)
710 } else {
711 l.value.cmp(&r.value)
713 }
714 }
715 (Term::Iri(l), Term::Iri(r)) => l.as_str().cmp(r.as_str()),
716 (Term::BlankNode(l), Term::BlankNode(r)) => l.as_str().cmp(r.as_str()),
717 (Term::QuotedTriple(l), Term::QuotedTriple(r)) => {
718 format!("{l}").cmp(&format!("{r}"))
720 }
721 (Term::PropertyPath(l), Term::PropertyPath(r)) => {
722 format!("{l}").cmp(&format!("{r}"))
724 }
725 (
728 Term::Literal(_),
729 Term::Iri(_)
730 | Term::BlankNode(_)
731 | Term::QuotedTriple(_)
732 | Term::PropertyPath(_)
733 | Term::Variable(_),
734 ) => Ordering::Less,
735 (Term::Iri(_), Term::Literal(_)) => Ordering::Greater,
736 (
737 Term::Iri(_),
738 Term::BlankNode(_)
739 | Term::QuotedTriple(_)
740 | Term::PropertyPath(_)
741 | Term::Variable(_),
742 ) => Ordering::Less,
743 (Term::BlankNode(_), Term::Literal(_) | Term::Iri(_)) => Ordering::Greater,
744 (
745 Term::BlankNode(_),
746 Term::QuotedTriple(_) | Term::PropertyPath(_) | Term::Variable(_),
747 ) => Ordering::Less,
748 (Term::QuotedTriple(_), Term::Literal(_) | Term::Iri(_) | Term::BlankNode(_)) => {
749 Ordering::Greater
750 }
751 (Term::QuotedTriple(_), Term::PropertyPath(_) | Term::Variable(_)) => Ordering::Less,
752 (
753 Term::PropertyPath(_),
754 Term::Literal(_) | Term::Iri(_) | Term::BlankNode(_) | Term::QuotedTriple(_),
755 ) => Ordering::Greater,
756 (Term::PropertyPath(_), Term::Variable(_)) => Ordering::Less,
757 (Term::Variable(_), _) => Ordering::Greater, }
759 }
760
761 fn merge_sorted_solutions(
763 &self,
764 left_solutions: Vec<Solution>,
765 right_solutions: Vec<Solution>,
766 join_variables: &[Variable],
767 ) -> Result<Vec<Solution>> {
768 let mut result = Vec::new();
769 let mut left_idx = 0;
770 let mut right_idx = 0;
771
772 while left_idx < left_solutions.len() && right_idx < right_solutions.len() {
773 let left_solution = &left_solutions[left_idx];
774 let right_solution = &right_solutions[right_idx];
775
776 let cmp =
777 self.compare_solutions_by_join_key(left_solution, right_solution, join_variables);
778
779 match cmp {
780 std::cmp::Ordering::Equal => {
781 let mut left_end = left_idx + 1;
783 while left_end < left_solutions.len()
784 && self.compare_solutions_by_join_key(
785 left_solution,
786 &left_solutions[left_end],
787 join_variables,
788 ) == std::cmp::Ordering::Equal
789 {
790 left_end += 1;
791 }
792
793 let mut right_end = right_idx + 1;
794 while right_end < right_solutions.len()
795 && self.compare_solutions_by_join_key(
796 right_solution,
797 &right_solutions[right_end],
798 join_variables,
799 ) == std::cmp::Ordering::Equal
800 {
801 right_end += 1;
802 }
803
804 for left_solution in left_solutions
806 .iter()
807 .skip(left_idx)
808 .take(left_end - left_idx)
809 {
810 for right_solution in right_solutions
811 .iter()
812 .skip(right_idx)
813 .take(right_end - right_idx)
814 {
815 if let Ok(Some(merged_solution)) = self.merge_solutions_if_compatible(
816 left_solution,
817 right_solution,
818 join_variables,
819 ) {
820 result.push(merged_solution);
821 }
822 }
823 }
824
825 left_idx = left_end;
826 right_idx = right_end;
827 }
828 std::cmp::Ordering::Less => {
829 left_idx += 1;
830 }
831 std::cmp::Ordering::Greater => {
832 right_idx += 1;
833 }
834 }
835 }
836
837 Ok(result)
838 }
839
840 fn merge_solutions_if_compatible(
842 &self,
843 left: &Solution,
844 right: &Solution,
845 join_variables: &[Variable],
846 ) -> Result<Option<Solution>> {
847 let mut result = Vec::new();
848
849 for left_binding in left {
850 for right_binding in right {
851 let mut compatible = true;
853 for var in join_variables {
854 if let (Some(left_term), Some(right_term)) =
855 (left_binding.get(var), right_binding.get(var))
856 {
857 if left_term != right_term {
858 compatible = false;
859 break;
860 }
861 }
862 }
863
864 if compatible {
865 let mut merged_binding = left_binding.clone();
867 for (var, term) in right_binding {
868 if !merged_binding.contains_key(var) {
870 merged_binding.insert(var.clone(), term.clone());
871 }
872 }
873 result.push(merged_binding);
874 }
875 }
876 }
877
878 if result.is_empty() {
879 Ok(None)
880 } else {
881 Ok(Some(result))
882 }
883 }
884}
885
886pub struct CacheFriendlyStorage {
888 columns: HashMap<Variable, Vec<Term>>,
890 row_count: usize,
892}
893
894impl Default for CacheFriendlyStorage {
895 fn default() -> Self {
896 Self::new()
897 }
898}
899
900impl CacheFriendlyStorage {
901 pub fn new() -> Self {
903 Self {
904 columns: HashMap::new(),
905 row_count: 0,
906 }
907 }
908
909 pub fn add_solutions(&mut self, solutions: &[Solution]) {
911 for solution in solutions {
912 for binding in solution {
913 for (var, term) in binding {
914 self.columns
915 .entry(var.clone())
916 .or_default()
917 .push(term.clone());
918 }
919 }
920 self.row_count += solution.len();
921 }
922 }
923
924 pub fn get_column(&self, var: &Variable) -> Option<&Vec<Term>> {
926 self.columns.get(var)
927 }
928
929 pub fn to_solutions(&self) -> Vec<Solution> {
931 let mut solutions = Vec::new();
932
933 if self.row_count == 0 {
934 return solutions;
935 }
936
937 for i in 0..self.row_count {
940 let mut binding = HashMap::new();
941 for (var, column) in &self.columns {
942 if let Some(term) = column.get(i) {
943 binding.insert(var.clone(), term.clone());
944 }
945 }
946 if !binding.is_empty() {
947 solutions.push(vec![binding]);
948 }
949 }
950
951 solutions
952 }
953}
954
955#[cfg(test)]
956mod tests {
957 use super::*;
958 use crate::algebra::Variable;
959 use oxirs_core::model::NamedNode;
960
961 #[test]
962 fn test_lock_free_queue() {
963 let queue = LockFreeWorkStealingQueue::new(16);
964
965 queue.push(42).unwrap();
967 queue.push(43).unwrap();
968
969 assert_eq!(queue.pop(), Some(43));
970 assert_eq!(queue.pop(), Some(42));
971 assert_eq!(queue.pop(), None);
972 }
973
974 #[test]
975 fn test_memory_pool() {
976 let pool = MemoryPool::new(2, 10, HashMap::<String, i32>::new);
977
978 let mut obj1 = pool.acquire();
979 obj1.get_mut().insert("test".to_string(), 42);
980
981 let obj2 = pool.acquire();
982 assert_ne!(obj1.get().len(), obj2.get().len());
983 }
984
985 #[test]
986 fn test_cache_friendly_hash_join() {
987 let join = CacheFriendlyHashJoin::new(4);
988
989 let var_x = Variable::new("x").unwrap();
991 let var_y = Variable::new("y").unwrap();
992
993 let mut left_binding = HashMap::new();
994 left_binding.insert(
995 var_x.clone(),
996 Term::Iri(NamedNode::new("http://example.org/1").unwrap()),
997 );
998 left_binding.insert(
999 var_y.clone(),
1000 Term::Iri(NamedNode::new("http://example.org/a").unwrap()),
1001 );
1002 let left_solutions = vec![vec![left_binding]];
1003
1004 let mut right_binding = HashMap::new();
1005 right_binding.insert(
1006 var_x.clone(),
1007 Term::Iri(NamedNode::new("http://example.org/1").unwrap()),
1008 );
1009 let right_solutions = vec![vec![right_binding]];
1010
1011 let results = join
1012 .join_parallel(left_solutions, right_solutions, &[var_x])
1013 .unwrap();
1014 assert!(!results.is_empty());
1015 }
1016
1017 #[test]
1018 fn test_simd_ops() {
1019 let strings = vec![
1020 "hello world".to_string(),
1021 "foo bar".to_string(),
1022 "hello rust".to_string(),
1023 ];
1024
1025 let results = SIMDOptimizedOps::bulk_string_compare(&strings, "hello");
1026 assert_eq!(results, vec![true, false, true]);
1027 }
1028
1029 #[test]
1030 fn test_cache_friendly_storage() {
1031 let mut storage = CacheFriendlyStorage::new();
1032
1033 let var_x = Variable::new("x").unwrap();
1034 let mut binding = HashMap::new();
1035 binding.insert(
1036 var_x.clone(),
1037 Term::Iri(NamedNode::new("http://example.org/1").unwrap()),
1038 );
1039 let solutions = vec![vec![binding]];
1040
1041 storage.add_solutions(&solutions);
1042 assert!(storage.get_column(&var_x).is_some());
1043
1044 let recovered = storage.to_solutions();
1045 assert_eq!(recovered.len(), 1);
1046 }
1047}