1use crate::algebra::{
11 Aggregate, Algebra, Binding, Expression, Literal, PropertyPath, Solution, Term as AlgebraTerm,
12 TriplePattern, Variable,
13};
14use crate::executor::stats::ExecutionStats;
15use crate::executor::{Dataset, ExecutionContext, ParallelConfig};
16use crate::expression::ExpressionEvaluator;
17use crate::parallel_types::ParallelStats;
18use crate::term::{BindingContext, Term};
19use anyhow::{anyhow, Result};
20use dashmap::DashMap;
21use oxirs_core::model::NamedNode;
22use parking_lot::RwLock;
23use rayon::prelude::*;
24use std::collections::{HashMap, HashSet};
25use std::sync::Arc;
26use std::time::Instant;
27
28pub struct ParallelQueryExecutor {
30 pub(crate) config: ParallelConfig,
31 pub(crate) stats: Arc<RwLock<ParallelStats>>,
32 pub(crate) thread_pool: rayon::ThreadPool,
33}
34
35pub type ParallelExecutor = ParallelQueryExecutor;
37
38fn normalize_bound_term(term: &AlgebraTerm) -> AlgebraTerm {
44 match term {
45 AlgebraTerm::PropertyPath(PropertyPath::Iri(n)) => AlgebraTerm::Iri(n.clone()),
46 other => other.clone(),
47 }
48}
49
50impl ParallelQueryExecutor {
51 pub fn new(config: ParallelConfig) -> Result<Self> {
53 let thread_pool = rayon::ThreadPoolBuilder::new()
54 .num_threads(config.max_threads)
55 .thread_name(|idx| format!("oxirs-arq-worker-{idx}"))
56 .stack_size(
57 config
58 .thread_pool_config
59 .stack_size
60 .unwrap_or(8 * 1024 * 1024),
61 )
62 .build()
63 .map_err(|e| anyhow!("Failed to create thread pool: {}", e))?;
64
65 Ok(Self {
66 config,
67 stats: Arc::new(RwLock::new(ParallelStats::default())),
68 thread_pool,
69 })
70 }
71
72 pub fn execute(
74 &self,
75 algebra: &Algebra,
76 dataset: &dyn Dataset,
77 context: &ExecutionContext,
78 stats: &mut ExecutionStats,
79 ) -> Result<Solution> {
80 let start = Instant::now();
81
82 {
84 let mut pstats = self.stats.write();
85 pstats.parallel_operations += 1;
86 }
87
88 let result = self
89 .thread_pool
90 .install(|| self.execute_parallel_internal(algebra, dataset, context, stats))?;
91
92 let _parallel_time = start.elapsed();
94 {
95 let mut pstats = self.stats.write();
96 pstats.thread_utilization = self.calculate_thread_utilization();
97 }
98
99 Ok(result)
100 }
101
102 pub(crate) fn execute_parallel_internal(
104 &self,
105 algebra: &Algebra,
106 dataset: &dyn Dataset,
107 context: &ExecutionContext,
108 stats: &mut ExecutionStats,
109 ) -> Result<Solution> {
110 match algebra {
111 Algebra::Bgp(patterns) => self.execute_parallel_bgp(patterns, dataset, stats),
112 Algebra::Join { left, right } => {
113 self.execute_parallel_join(left, right, dataset, context, stats)
114 }
115 Algebra::Union { left, right } => {
116 self.execute_parallel_union(left, right, dataset, context, stats)
117 }
118 Algebra::Filter { pattern, condition } => {
119 self.execute_parallel_filter(pattern, condition, dataset, context, stats)
120 }
121 Algebra::OrderBy {
122 pattern,
123 conditions,
124 } => {
125 let conditions_tuple: Vec<(Expression, bool)> = conditions
126 .iter()
127 .map(|c| (c.expr.clone(), c.ascending))
128 .collect();
129 self.execute_parallel_order_by(pattern, &conditions_tuple, dataset, context, stats)
130 }
131 Algebra::Group {
132 pattern,
133 variables,
134 aggregates,
135 } => {
136 let group_vars: Vec<Variable> = variables
137 .iter()
138 .filter_map(|gc| {
139 gc.alias.clone().or_else(|| {
140 if let Expression::Variable(var) = &gc.expr {
141 Some(var.clone())
142 } else {
143 None
144 }
145 })
146 })
147 .collect();
148 self.execute_parallel_group(
149 pattern,
150 &group_vars,
151 aggregates,
152 dataset,
153 context,
154 stats,
155 )
156 }
157 Algebra::PropertyPath {
158 subject,
159 path,
160 object,
161 } => {
162 self.execute_parallel_property_path(subject, path, object, dataset, context, stats)
163 }
164 Algebra::LeftJoin {
165 left,
166 right,
167 filter,
168 } => self.execute_parallel_left_join(left, right, filter, dataset, context, stats),
169 Algebra::Extend {
170 pattern,
171 variable,
172 expr,
173 } => self.execute_parallel_extend(pattern, variable, expr, dataset, context, stats),
174 Algebra::Minus { left, right } => {
175 self.execute_parallel_minus(left, right, dataset, context, stats)
176 }
177 Algebra::Service {
178 endpoint,
179 pattern,
180 silent,
181 } => self.execute_parallel_service(endpoint, pattern, *silent, dataset, context, stats),
182 Algebra::Graph { graph, pattern } => {
183 self.execute_parallel_graph(graph, pattern, dataset, context, stats)
184 }
185 Algebra::Project { pattern, variables } => {
186 self.execute_parallel_project(pattern, variables, dataset, context, stats)
187 }
188 Algebra::Distinct { pattern } => {
189 self.execute_parallel_distinct(pattern, dataset, context, stats)
190 }
191 Algebra::Reduced { pattern } => {
192 self.execute_parallel_reduced(pattern, dataset, context, stats)
193 }
194 Algebra::Slice {
195 pattern,
196 offset,
197 limit,
198 } => self.execute_parallel_slice(pattern, *offset, *limit, dataset, context, stats),
199 Algebra::Values { bindings, .. } => Ok(bindings.clone()),
203 Algebra::Table => Ok(vec![Binding::new()]),
204 Algebra::Zero | Algebra::Empty => Ok(Vec::new()),
205 _ => {
206 Err(anyhow!(
208 "Parallel execution not supported for this algebra type"
209 ))
210 }
211 }
212 }
213
214 pub(crate) fn execute_parallel_bgp(
216 &self,
217 patterns: &[TriplePattern],
218 dataset: &dyn Dataset,
219 stats: &mut ExecutionStats,
220 ) -> Result<Solution> {
221 if patterns.is_empty() {
222 return Ok(vec![HashMap::new()]);
223 }
224
225 let solution = self.process_bgp_chunk(patterns, dataset)?;
235 self.merge_bgp_results(vec![solution], stats)
236 }
237
238 fn process_bgp_chunk(
240 &self,
241 patterns: &[TriplePattern],
242 dataset: &dyn Dataset,
243 ) -> Result<Solution> {
244 let mut solution = vec![HashMap::new()];
245
246 for pattern in patterns {
247 solution = self.join_with_pattern_parallel(solution, pattern, dataset)?;
248 if solution.is_empty() {
249 break;
250 }
251 }
252
253 Ok(solution)
254 }
255
256 fn join_with_pattern_parallel(
258 &self,
259 solution: Solution,
260 pattern: &TriplePattern,
261 dataset: &dyn Dataset,
262 ) -> Result<Solution> {
263 if solution.len() > self.config.parallel_threshold {
265 let results: Vec<Binding> = solution
269 .par_iter()
270 .map(|binding| self.extend_binding_with_pattern(binding, pattern, dataset))
271 .collect::<Result<Vec<Vec<Binding>>>>()?
272 .into_iter()
273 .flatten()
274 .collect();
275 Ok(results)
276 } else {
277 let mut result = Vec::new();
279 for binding in solution {
280 let extensions = self.extend_binding_with_pattern(&binding, pattern, dataset)?;
281 result.extend(extensions);
282 }
283 Ok(result)
284 }
285 }
286
287 fn extend_binding_with_pattern(
289 &self,
290 binding: &Binding,
291 pattern: &TriplePattern,
292 dataset: &dyn Dataset,
293 ) -> Result<Vec<Binding>> {
294 let instantiated = self.instantiate_pattern(pattern, binding);
295 let triples = dataset.find_triples(&instantiated)?;
296
297 let mut results = Vec::new();
298 for (s, p, o) in triples {
299 if let Some(new_binding) = self.try_extend_binding(binding, pattern, &s, &p, &o) {
300 results.push(new_binding);
301 }
302 }
303
304 Ok(results)
305 }
306
307 fn instantiate_pattern(&self, pattern: &TriplePattern, binding: &Binding) -> TriplePattern {
309 TriplePattern {
310 subject: self.instantiate_term(&pattern.subject, binding),
311 predicate: self.instantiate_term(&pattern.predicate, binding),
312 object: self.instantiate_term(&pattern.object, binding),
313 }
314 }
315
316 fn instantiate_term(&self, term: &AlgebraTerm, binding: &Binding) -> AlgebraTerm {
318 match term {
319 AlgebraTerm::Variable(var) => binding.get(var).cloned().unwrap_or_else(|| term.clone()),
320 _ => term.clone(),
321 }
322 }
323
324 fn try_extend_binding(
326 &self,
327 binding: &Binding,
328 pattern: &TriplePattern,
329 s: &AlgebraTerm,
330 p: &AlgebraTerm,
331 o: &AlgebraTerm,
332 ) -> Option<Binding> {
333 let mut new_binding = binding.clone();
334
335 if !self.try_bind(&mut new_binding, &pattern.subject, s)
336 || !self.try_bind(&mut new_binding, &pattern.predicate, p)
337 || !self.try_bind(&mut new_binding, &pattern.object, o)
338 {
339 return None;
340 }
341
342 Some(new_binding)
343 }
344
345 fn try_bind(
347 &self,
348 binding: &mut Binding,
349 pattern_term: &AlgebraTerm,
350 value: &AlgebraTerm,
351 ) -> bool {
352 match pattern_term {
353 AlgebraTerm::Variable(var) => {
354 if let Some(existing) = binding.get(var) {
355 existing == value
356 } else {
357 binding.insert(var.clone(), value.clone());
358 true
359 }
360 }
361 _ => normalize_bound_term(pattern_term) == normalize_bound_term(value),
369 }
370 }
371
372 pub(crate) fn merge_bgp_results(
374 &self,
375 partial_results: Vec<Solution>,
376 stats: &mut ExecutionStats,
377 ) -> Result<Solution> {
378 let merged = partial_results
380 .into_par_iter()
381 .reduce(Vec::new, |mut acc, mut partial| {
382 acc.append(&mut partial);
383 acc
384 });
385
386 stats.intermediate_results += merged.len();
387 Ok(merged)
388 }
389
390 fn execute_parallel_join(
392 &self,
393 left: &Algebra,
394 right: &Algebra,
395 dataset: &dyn Dataset,
396 context: &ExecutionContext,
397 stats: &mut ExecutionStats,
398 ) -> Result<Solution> {
399 let left_solution = self.execute_parallel_internal(left, dataset, context, stats)?;
401 let right_solution = self.execute_parallel_internal(right, dataset, context, stats)?;
402
403 let join_vars = self.find_join_variables(&left_solution, &right_solution);
405
406 if join_vars.is_empty() {
407 self.parallel_cartesian_product(left_solution, right_solution, stats)
409 } else {
410 self.parallel_hash_join(left_solution, right_solution, join_vars, stats)
412 }
413 }
414
415 fn find_join_variables(&self, left: &Solution, right: &Solution) -> Vec<Variable> {
417 if left.is_empty() || right.is_empty() {
418 return vec![];
419 }
420
421 let left_vars: HashSet<_> = left[0].keys().cloned().collect();
422 let right_vars: HashSet<_> = right[0].keys().cloned().collect();
423
424 left_vars.intersection(&right_vars).cloned().collect()
425 }
426
427 fn parallel_hash_join(
429 &self,
430 left: Solution,
431 right: Solution,
432 join_vars: Vec<Variable>,
433 stats: &mut ExecutionStats,
434 ) -> Result<Solution> {
435 let (build_side, probe_side) = if left.len() <= right.len() {
437 (left, right)
438 } else {
439 (right, left)
440 };
441
442 let hash_table: DashMap<Vec<AlgebraTerm>, Vec<Binding>> = DashMap::new();
444
445 build_side.par_iter().for_each(|binding| {
451 let key: Vec<AlgebraTerm> = join_vars
452 .iter()
453 .filter_map(|var| binding.get(var).cloned())
454 .collect();
455 hash_table.entry(key).or_default().push(binding.clone());
456 });
457
458 let result: Vec<Binding> = probe_side
460 .par_iter()
461 .flat_map(|probe_binding| {
462 let key: Vec<AlgebraTerm> = join_vars
463 .iter()
464 .filter_map(|var| probe_binding.get(var).cloned())
465 .collect();
466
467 match hash_table.get(&key) {
468 Some(matches) => matches
469 .iter()
470 .filter_map(|build_binding| {
471 self.merge_bindings(build_binding, probe_binding)
472 })
473 .collect::<Vec<_>>(),
474 _ => {
475 vec![]
476 }
477 }
478 })
479 .collect();
480
481 stats.intermediate_results += result.len();
482 Ok(result)
483 }
484
485 pub(crate) fn merge_bindings(&self, left: &Binding, right: &Binding) -> Option<Binding> {
487 let mut merged = left.clone();
488
489 for (var, value) in right {
490 if let Some(existing) = merged.get(var) {
491 if existing != value {
492 return None;
493 }
494 } else {
495 merged.insert(var.clone(), value.clone());
496 }
497 }
498
499 Some(merged)
500 }
501
502 fn parallel_cartesian_product(
504 &self,
505 left: Solution,
506 right: Solution,
507 stats: &mut ExecutionStats,
508 ) -> Result<Solution> {
509 let result: Vec<Binding> = left
510 .par_iter()
511 .flat_map(|l| {
512 right
513 .iter()
514 .filter_map(|r| self.merge_bindings(l, r))
515 .collect::<Vec<_>>()
516 })
517 .collect();
518
519 stats.intermediate_results += result.len();
520 Ok(result)
521 }
522
523 fn execute_parallel_union(
525 &self,
526 left: &Algebra,
527 right: &Algebra,
528 dataset: &dyn Dataset,
529 context: &ExecutionContext,
530 stats: &mut ExecutionStats,
531 ) -> Result<Solution> {
532 let left_result = self.execute_parallel_internal(left, dataset, context, stats)?;
534 let right_result = self.execute_parallel_internal(right, dataset, context, stats)?;
535
536 let mut result = left_result;
541 result.extend(right_result);
542
543 stats.intermediate_results += result.len();
544 Ok(result)
545 }
546
547 pub(crate) fn parallel_distinct(&self, solution: Solution) -> Solution {
549 let seen: DashMap<Vec<(crate::algebra::Variable, String)>, ()> = DashMap::new();
553
554 solution
555 .into_par_iter()
556 .filter(|binding| {
557 let mut key: Vec<_> = binding
558 .iter()
559 .map(|(var, term)| (var.clone(), format!("{term:?}")))
560 .collect();
561 key.sort();
562 seen.insert(key, ()).is_none()
563 })
564 .collect()
565 }
566
567 fn execute_parallel_filter(
569 &self,
570 pattern: &Algebra,
571 condition: &Expression,
572 dataset: &dyn Dataset,
573 context: &ExecutionContext,
574 stats: &mut ExecutionStats,
575 ) -> Result<Solution> {
576 let solution = self.execute_parallel_internal(pattern, dataset, context, stats)?;
577
578 let extension_registry = context.extension_registry.clone();
580
581 let filtered: Vec<Binding> = solution
598 .into_par_iter()
599 .map(|binding| -> Result<Option<Binding>> {
600 let mut ctx = BindingContext::new();
601 for (var, term) in &binding {
602 ctx.bind(var.as_str(), Term::from_algebra_term(term));
603 }
604 let evaluator_with_ctx =
605 ExpressionEvaluator::with_context(extension_registry.clone(), ctx);
606 match evaluator_with_ctx.evaluate(condition) {
607 Ok(term) => {
608 if term.effective_boolean_value().unwrap_or(false) {
609 Ok(Some(binding))
610 } else {
611 Ok(None)
612 }
613 }
614 Err(e) => {
615 if e.downcast_ref::<crate::executor::UnknownFunctionError>()
616 .is_some()
617 || e.downcast_ref::<crate::query_governor::BudgetExceeded>()
618 .is_some()
619 {
620 Err(e)
621 } else {
622 Ok(None)
623 }
624 }
625 }
626 })
627 .collect::<Result<Vec<Option<Binding>>>>()?
628 .into_iter()
629 .flatten()
630 .collect();
631
632 stats.intermediate_results += filtered.len();
633 Ok(filtered)
634 }
635
636 fn execute_parallel_order_by(
638 &self,
639 pattern: &Algebra,
640 conditions: &[(Expression, bool)], dataset: &dyn Dataset,
642 context: &ExecutionContext,
643 stats: &mut ExecutionStats,
644 ) -> Result<Solution> {
645 let mut solution = self.execute_parallel_internal(pattern, dataset, context, stats)?;
646
647 let extension_registry = context.extension_registry.clone();
649
650 solution.par_sort_by(|a, b| {
652 for (expr, ascending) in conditions {
653 let mut ctx_a = BindingContext::new();
655 let mut ctx_b = BindingContext::new();
656 for (var, term) in a {
657 ctx_a.bind(var.as_str(), Term::from_algebra_term(term));
658 }
659 for (var, term) in b {
660 ctx_b.bind(var.as_str(), Term::from_algebra_term(term));
661 }
662
663 let evaluator_a =
664 ExpressionEvaluator::with_context(extension_registry.clone(), ctx_a);
665 let evaluator_b =
666 ExpressionEvaluator::with_context(extension_registry.clone(), ctx_b);
667
668 let val_a = evaluator_a.evaluate(expr).ok();
669 let val_b = evaluator_b.evaluate(expr).ok();
670
671 match (val_a, val_b) {
672 (Some(a_term), Some(b_term)) => {
673 let alg_a = a_term.to_algebra_term();
674 let alg_b = b_term.to_algebra_term();
675 let cmp = match (&alg_a, &alg_b) {
680 (AlgebraTerm::Literal(la), AlgebraTerm::Literal(lb)) => {
681 crate::executor::queryexecutor_apply_order_by_group::compare_literals(la, lb)
682 }
683 (AlgebraTerm::Iri(ia), AlgebraTerm::Iri(ib)) => {
684 ia.as_str().cmp(ib.as_str())
685 }
686 _ => self.compare_algebra_terms(&alg_a, &alg_b),
687 };
688 if cmp != std::cmp::Ordering::Equal {
689 return if *ascending { cmp } else { cmp.reverse() };
690 }
691 }
692 (Some(_), None) => {
695 return if *ascending {
696 std::cmp::Ordering::Greater
697 } else {
698 std::cmp::Ordering::Less
699 }
700 }
701 (None, Some(_)) => {
702 return if *ascending {
703 std::cmp::Ordering::Less
704 } else {
705 std::cmp::Ordering::Greater
706 }
707 }
708 (None, None) => continue,
709 }
710 }
711 std::cmp::Ordering::Equal
712 });
713
714 Ok(solution)
715 }
716
717 pub(crate) fn compare_algebra_terms(
719 &self,
720 a: &AlgebraTerm,
721 b: &AlgebraTerm,
722 ) -> std::cmp::Ordering {
723 let term_a = Term::from_algebra_term(a);
725 let term_b = Term::from_algebra_term(b);
726 term_a
727 .partial_cmp(&term_b)
728 .unwrap_or(std::cmp::Ordering::Equal)
729 }
730
731 fn execute_parallel_group(
733 &self,
734 pattern: &Algebra,
735 variables: &[Variable],
736 aggregates: &[(Variable, Aggregate)],
737 dataset: &dyn Dataset,
738 context: &ExecutionContext,
739 stats: &mut ExecutionStats,
740 ) -> Result<Solution> {
741 let solution = self.execute_parallel_internal(pattern, dataset, context, stats)?;
742
743 let groups: DashMap<Vec<AlgebraTerm>, Vec<Binding>> = DashMap::new();
745
746 solution.par_iter().for_each(|binding| {
747 let key: Vec<AlgebraTerm> = variables
748 .iter()
749 .map(|var| {
750 binding
751 .get(var)
752 .cloned()
753 .unwrap_or(AlgebraTerm::Variable(var.clone()))
754 })
755 .collect();
756
757 groups.entry(key).or_default().push(binding.clone());
758 });
759
760 let groups_vec: Vec<(Vec<AlgebraTerm>, Vec<Binding>)> = groups.into_iter().collect();
762
763 let result: Vec<Binding> = groups_vec
764 .into_par_iter()
765 .map(|(key, group)| {
766 let mut result_binding = HashMap::new();
767
768 for (i, var) in variables.iter().enumerate() {
770 if let Some(term) = key.get(i) {
771 if !matches!(term, AlgebraTerm::Variable(_)) {
772 result_binding.insert(var.clone(), term.clone());
773 }
774 }
775 }
776
777 for (var, agg) in aggregates {
779 if let Ok(value) = self.compute_aggregate(agg, &group, context) {
780 result_binding.insert(var.clone(), value);
781 }
782 }
783
784 result_binding
785 })
786 .collect();
787
788 stats.intermediate_results += result.len();
789 Ok(result)
790 }
791
792 fn compute_aggregate(
794 &self,
795 aggregate: &Aggregate,
796 group: &[Binding],
797 context: &ExecutionContext,
798 ) -> Result<AlgebraTerm> {
799 match aggregate {
800 Aggregate::Count { expr, distinct } => {
801 let values =
802 self.collect_aggregate_values(expr.as_ref(), group, *distinct, context)?;
803 Ok(AlgebraTerm::Literal(Literal::typed(
804 values.len().to_string(),
805 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#integer"),
806 )))
807 }
808 Aggregate::Sum { expr, distinct } => {
809 let values =
810 self.collect_aggregate_values(Some(expr), group, *distinct, context)?;
811 let sum = self.sum_numeric_values(values)?;
812 Ok(sum)
813 }
814 Aggregate::Min { expr, distinct } => {
815 let values =
816 self.collect_aggregate_values(Some(expr), group, *distinct, context)?;
817 values
818 .into_iter()
819 .min_by(|a, b| self.compare_algebra_terms(a, b))
820 .ok_or_else(|| anyhow!("No values for MIN"))
821 }
822 Aggregate::Max { expr, distinct } => {
823 let values =
824 self.collect_aggregate_values(Some(expr), group, *distinct, context)?;
825 values
826 .into_iter()
827 .max_by(|a, b| self.compare_algebra_terms(a, b))
828 .ok_or_else(|| anyhow!("No values for MAX"))
829 }
830 Aggregate::Avg { expr, distinct } => {
831 let values =
832 self.collect_aggregate_values(Some(expr), group, *distinct, context)?;
833 let sum = self.sum_numeric_values(values.clone())?;
834 let count = values.len() as f64;
835
836 match sum {
837 AlgebraTerm::Literal(lit) => {
838 let val = lit.value.parse::<f64>().unwrap_or(0.0);
839 Ok(AlgebraTerm::Literal(Literal::typed(
840 (val / count).to_string(),
841 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#decimal"),
842 )))
843 }
844 _ => Err(anyhow!("Invalid sum for AVG")),
845 }
846 }
847 Aggregate::GroupConcat {
848 expr,
849 separator,
850 distinct,
851 } => {
852 let values =
853 self.collect_aggregate_values(Some(expr), group, *distinct, context)?;
854 let sep = separator.as_deref().unwrap_or(" ");
855 let concat = values
856 .iter()
857 .map(|v| self.term_to_string(v))
858 .collect::<Vec<_>>()
859 .join(sep);
860 Ok(AlgebraTerm::Literal(Literal::string(concat)))
861 }
862 _ => Err(anyhow!("Unsupported aggregate")),
863 }
864 }
865
866 fn collect_aggregate_values(
868 &self,
869 expr: Option<&Expression>,
870 group: &[Binding],
871 distinct: bool,
872 context: &ExecutionContext,
873 ) -> Result<Vec<AlgebraTerm>> {
874 let extension_registry = context.extension_registry.clone();
875
876 let mut values: Vec<AlgebraTerm> = group
877 .par_iter()
878 .filter_map(|binding| {
879 if let Some(expr) = expr {
880 let mut ctx = BindingContext::new();
881 for (var, term) in binding {
882 ctx.bind(var.as_str(), Term::from_algebra_term(term));
883 }
884 let evaluator =
885 ExpressionEvaluator::with_context(extension_registry.clone(), ctx);
886 evaluator.evaluate(expr).ok().map(|t| t.to_algebra_term())
887 } else {
888 Some(AlgebraTerm::Literal(Literal::string("1")))
890 }
891 })
892 .collect();
893
894 if distinct {
895 values.sort();
896 values.dedup();
897 }
898
899 Ok(values)
900 }
901
902 fn sum_numeric_values(&self, values: Vec<AlgebraTerm>) -> Result<AlgebraTerm> {
904 let sum = values
905 .into_par_iter()
906 .filter_map(|term| {
907 if let AlgebraTerm::Literal(lit) = term {
908 lit.value.parse::<f64>().ok()
909 } else {
910 None
911 }
912 })
913 .sum::<f64>();
914
915 Ok(AlgebraTerm::Literal(Literal::typed(
916 sum.to_string(),
917 NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#decimal"),
918 )))
919 }
920
921 fn term_to_string(&self, term: &AlgebraTerm) -> String {
923 match term {
924 AlgebraTerm::Iri(iri) => iri.as_str().to_string(),
925 AlgebraTerm::Literal(lit) => lit.value.to_string(),
926 AlgebraTerm::Variable(var) => format!("?{var}"),
927 AlgebraTerm::BlankNode(id) => format!("_:{id}"),
928 AlgebraTerm::QuotedTriple(_) => "<<quoted triple>>".to_string(),
929 AlgebraTerm::PropertyPath(_) => "<<property path>>".to_string(),
930 }
931 }
932
933 fn calculate_thread_utilization(&self) -> f64 {
935 self.thread_pool.current_num_threads() as f64 / self.config.max_threads as f64
937 }
938
939 pub fn get_stats(&self) -> ParallelStats {
941 let stats = self.stats.read();
942 ParallelStats {
943 parallel_operations: stats.parallel_operations,
944 work_items_processed: stats.work_items_processed,
945 thread_utilization: stats.thread_utilization,
946 parallel_speedup: stats.parallel_speedup,
947 cache_hits: stats.cache_hits,
948 cache_misses: stats.cache_misses,
949 }
950 }
951}