1use crate::lexicon::generate_embedded_triple_id;
6use crate::rdf_star::is_virtual_id;
7use crate::sparql_aggregates::{AggregationContext, GroupKey};
8use crate::sparql_ast::*;
9use crate::sparql_filter::{EvalResult, ExpressionEvaluator};
10use crate::sparql_planner::*;
11use crate::NQuin;
12
13#[cfg(not(target_arch = "wasm32"))]
14mod range_q42_exec {
15 use super::*;
16
17#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
21pub struct Q42RangeSparqlCursor {
22 pub scan: crate::q42_volume::Q42RangeQueryCursor,
23}
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct Q42RangeSparqlPage {
27 pub returned: usize,
28 pub next_cursor: Option<Q42RangeSparqlCursor>,
29}
30
31#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
32pub struct Q42RangeVolumeSetSparqlCursor {
33 pub scan: crate::q42_volume::Q42VolumeSetQueryCursor,
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub struct Q42RangeVolumeSetSparqlPage {
38 pub returned: usize,
39 pub next_cursor: Option<Q42RangeVolumeSetSparqlCursor>,
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub struct Q42RangeTripleSelectPlan {
48 pub subject: u64,
49 pub predicate: u64,
50 pub object: u64,
51 pub projection: [VariableId; MAX_VARIABLES],
52 pub projection_count: u8,
53 pub filters: [ExpressionId; 8],
54 pub filter_count: u8,
55 pub limit: u64,
56 pub offset: u64,
57}
58
59#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
60pub struct Q42RangeTripleSelectCursor {
61 pub scan: Q42RangeSparqlCursor,
62 pub skipped: u64,
63 pub emitted: u64,
64}
65
66#[derive(Clone, Copy, Debug, Eq, PartialEq)]
67pub struct Q42RangeTripleSelectPage {
68 pub returned: usize,
69 pub next_cursor: Option<Q42RangeTripleSelectCursor>,
70}
71
72#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
73pub struct Q42RangeVolumeSetTripleSelectCursor {
74 pub scan: Q42RangeVolumeSetSparqlCursor,
75 pub skipped: u64,
76 pub emitted: u64,
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub struct Q42RangeVolumeSetTripleSelectPage {
81 pub returned: usize,
82 pub next_cursor: Option<Q42RangeVolumeSetTripleSelectCursor>,
83}
84
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub struct Q42RangeTriplePattern {
87 pub subject: u64,
88 pub predicate: u64,
89 pub object: u64,
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93pub struct Q42RangeNestedLoopJoinPlan {
94 pub left: Q42RangeTriplePattern,
95 pub right: Q42RangeTriplePattern,
96}
97
98#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
99pub struct Q42RangeNestedLoopJoinState {
100 pub left_scan: Q42RangeSparqlCursor,
101 pub right_scan: Q42RangeSparqlCursor,
102 pub left_count: usize,
103 pub left_index: usize,
104 pub left_exhausted: bool,
105 pub right_active: bool,
106}
107
108#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub struct Q42RangeNestedLoopJoinPage {
110 pub returned: usize,
111 pub done: bool,
112}
113
114#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
118pub struct Q42RangeVolumeSetNestedLoopJoinState {
119 pub left_scan: Q42RangeVolumeSetSparqlCursor,
120 pub right_scan: Q42RangeVolumeSetSparqlCursor,
121 pub left_count: usize,
122 pub left_index: usize,
123 pub left_exhausted: bool,
124 pub right_active: bool,
125}
126
127impl Q42RangeNestedLoopJoinPlan {
128 pub fn from_execution_plan(plan: &ExecutionPlan) -> Result<Self, String> {
129 Self::from_join_root(plan, plan.root_operator)
130 }
131
132 pub fn from_join_root(plan: &ExecutionPlan, root: OperatorId) -> Result<Self, String> {
133 if plan.operator_count == 0 || root as usize >= plan.operator_count as usize {
134 return Err("range join requires a non-empty execution plan".to_string());
135 }
136 let PhysicalOperatorType::NestedLoopJoin { left, right, .. } =
137 plan.operators[root as usize].operator_type
138 else {
139 return Err("range join currently requires a NestedLoopJoin".to_string());
140 };
141 let triple = |operator: OperatorId| -> Result<Q42RangeTriplePattern, String> {
142 let Some(entry) = plan.operators.get(operator as usize) else {
143 return Err("range join input operator is out of bounds".to_string());
144 };
145 match entry.operator_type {
146 PhysicalOperatorType::TripleScan {
147 subject,
148 predicate,
149 object,
150 } => Ok(Q42RangeTriplePattern {
151 subject,
152 predicate,
153 object,
154 }),
155 _ => Err("range join currently requires TripleScan inputs".to_string()),
156 }
157 };
158 Ok(Self {
159 left: triple(left)?,
160 right: triple(right)?,
161 })
162 }
163}
164
165pub fn execute_range_nested_loop_join_page_into<S: crate::q42_volume::Q42RangeSource>(
170 volume: &crate::q42_volume::Q42RangeVolume<S>,
171 plan: Q42RangeNestedLoopJoinPlan,
172 ctx: &SparqlQueryContext,
173 state: &mut Q42RangeNestedLoopJoinState,
174 compressed: &mut [u8],
175 decoded: &mut [u8],
176 quin_scratch: &mut [NQuin],
177 left_rows: &mut [BindingRow],
178 right_rows: &mut [BindingRow],
179 out: &mut [BindingRow],
180) -> Result<Q42RangeNestedLoopJoinPage, String> {
181 if quin_scratch.is_empty()
182 || left_rows.len() < quin_scratch.len()
183 || right_rows.len() < quin_scratch.len()
184 || out.len() < quin_scratch.len()
185 {
186 return Err(
187 "range nested-loop join requires row buffers at least as large as Quin scratch"
188 .to_string(),
189 );
190 }
191 let mut returned = 0usize;
192 loop {
193 if returned + quin_scratch.len() > out.len() {
194 return Ok(Q42RangeNestedLoopJoinPage {
195 returned,
196 done: false,
197 });
198 }
199 if state.left_index >= state.left_count {
200 if state.left_exhausted {
201 return Ok(Q42RangeNestedLoopJoinPage {
202 returned,
203 done: true,
204 });
205 }
206 let page = execute_range_triple_page_into(
207 volume,
208 plan.left.subject,
209 plan.left.predicate,
210 plan.left.object,
211 None,
212 ctx,
213 &BindingRow::default(),
214 state.left_scan,
215 compressed,
216 decoded,
217 quin_scratch,
218 left_rows,
219 )?;
220 state.left_count = page.returned;
221 state.left_index = 0;
222 state.left_scan = page.next_cursor.unwrap_or_default();
223 state.left_exhausted = page.next_cursor.is_none();
224 state.right_active = false;
225 if state.left_count == 0 {
226 if state.left_exhausted {
227 return Ok(Q42RangeNestedLoopJoinPage {
228 returned,
229 done: true,
230 });
231 }
232 continue;
233 }
234 }
235 let input = left_rows[state.left_index];
236 let page = execute_range_triple_page_into(
237 volume,
238 plan.right.subject,
239 plan.right.predicate,
240 plan.right.object,
241 None,
242 ctx,
243 &input,
244 if state.right_active {
245 state.right_scan
246 } else {
247 Q42RangeSparqlCursor::default()
248 },
249 compressed,
250 decoded,
251 quin_scratch,
252 right_rows,
253 )?;
254 out[returned..returned + page.returned].copy_from_slice(&right_rows[..page.returned]);
255 returned += page.returned;
256 match page.next_cursor {
257 Some(next) => {
258 state.right_scan = next;
259 state.right_active = true;
260 }
261 None => {
262 state.left_index += 1;
263 state.right_scan = Q42RangeSparqlCursor::default();
264 state.right_active = false;
265 }
266 }
267 if returned + quin_scratch.len() > out.len() {
268 return Ok(Q42RangeNestedLoopJoinPage {
269 returned,
270 done: false,
271 });
272 }
273 }
274}
275
276pub fn execute_range_volume_set_nested_loop_join_page_into<S: crate::q42_volume::Q42RangeSource>(
281 volumes: &crate::q42_volume::Q42RangeVolumeSet<S>,
282 plan: Q42RangeNestedLoopJoinPlan,
283 ctx: &SparqlQueryContext,
284 state: &mut Q42RangeVolumeSetNestedLoopJoinState,
285 compressed: &mut [u8],
286 decoded: &mut [u8],
287 quin_scratch: &mut [NQuin],
288 left_rows: &mut [BindingRow],
289 right_rows: &mut [BindingRow],
290 out: &mut [BindingRow],
291) -> Result<Q42RangeNestedLoopJoinPage, String> {
292 if quin_scratch.is_empty()
293 || left_rows.len() < quin_scratch.len()
294 || right_rows.len() < quin_scratch.len()
295 || out.len() < quin_scratch.len()
296 {
297 return Err(
298 "range nested-loop join requires row buffers at least as large as Quin scratch"
299 .to_string(),
300 );
301 }
302 let mut returned = 0usize;
303 loop {
304 if returned + quin_scratch.len() > out.len() {
305 return Ok(Q42RangeNestedLoopJoinPage {
306 returned,
307 done: false,
308 });
309 }
310 if state.left_index >= state.left_count {
311 if state.left_exhausted {
312 return Ok(Q42RangeNestedLoopJoinPage {
313 returned,
314 done: true,
315 });
316 }
317 let page = execute_range_volume_set_triple_page_into(
318 volumes,
319 plan.left.subject,
320 plan.left.predicate,
321 plan.left.object,
322 None,
323 ctx,
324 &BindingRow::default(),
325 state.left_scan,
326 compressed,
327 decoded,
328 quin_scratch,
329 left_rows,
330 )?;
331 state.left_count = page.returned;
332 state.left_index = 0;
333 state.left_scan = page.next_cursor.unwrap_or_default();
334 state.left_exhausted = page.next_cursor.is_none();
335 state.right_active = false;
336 if state.left_count == 0 {
337 if state.left_exhausted {
338 return Ok(Q42RangeNestedLoopJoinPage {
339 returned,
340 done: true,
341 });
342 }
343 continue;
344 }
345 }
346 let input = left_rows[state.left_index];
347 let page = execute_range_volume_set_triple_page_into(
348 volumes,
349 plan.right.subject,
350 plan.right.predicate,
351 plan.right.object,
352 None,
353 ctx,
354 &input,
355 if state.right_active {
356 state.right_scan
357 } else {
358 Q42RangeVolumeSetSparqlCursor::default()
359 },
360 compressed,
361 decoded,
362 quin_scratch,
363 right_rows,
364 )?;
365 out[returned..returned + page.returned].copy_from_slice(&right_rows[..page.returned]);
366 returned += page.returned;
367 match page.next_cursor {
368 Some(next) => {
369 state.right_scan = next;
370 state.right_active = true;
371 }
372 None => {
373 state.left_index += 1;
374 state.right_scan = Q42RangeVolumeSetSparqlCursor::default();
375 state.right_active = false;
376 }
377 }
378 if returned + quin_scratch.len() > out.len() {
379 return Ok(Q42RangeNestedLoopJoinPage {
380 returned,
381 done: false,
382 });
383 }
384 }
385}
386
387impl Q42RangeTripleSelectPlan {
388 pub fn from_execution_plan(plan: &ExecutionPlan) -> Result<Self, String> {
389 if plan.operator_count == 0 || plan.root_operator as usize >= plan.operator_count as usize {
390 return Err("range SPARQL requires a non-empty execution plan".to_string());
391 }
392 let mut operator = plan.root_operator;
393 let mut projection = [0; MAX_VARIABLES];
394 let mut projection_count = 0;
395 let mut filters = [0; 8];
396 let mut filter_count = 0usize;
397 let mut limit = u64::MAX;
398 let mut offset = 0;
399 loop {
400 match plan.operators[operator as usize].operator_type {
401 PhysicalOperatorType::Project { input, vars, var_count } => {
402 if projection_count != 0 { return Err("range SPARQL does not support nested projections".to_string()); }
403 projection = vars;
404 projection_count = var_count;
405 operator = input;
406 }
407 PhysicalOperatorType::Limit { input, limit: configured, offset: configured_offset } => {
408 if limit != u64::MAX || offset != 0 { return Err("range SPARQL does not support nested limits".to_string()); }
409 limit = configured;
410 offset = configured_offset;
411 operator = input;
412 }
413 PhysicalOperatorType::Filter { input, expression } => {
414 if filter_count == filters.len() {
415 return Err("range SPARQL supports at most eight stacked FILTER operators".to_string());
416 }
417 filters[filter_count] = expression;
418 filter_count += 1;
419 operator = input;
420 }
421 PhysicalOperatorType::TripleScan { subject, predicate, object } => {
422 return Ok(Self { subject, predicate, object, projection, projection_count, filters, filter_count: filter_count as u8, limit, offset });
423 }
424 _ => return Err("range SPARQL currently supports one TripleScan with optional PROJECT and LIMIT/OFFSET".to_string()),
425 }
426 }
427 }
428}
429
430pub fn execute_range_triple_select_page_into<S: crate::q42_volume::Q42RangeSource>(
432 volume: &crate::q42_volume::Q42RangeVolume<S>,
433 plan: Q42RangeTripleSelectPlan,
434 ctx: &SparqlQueryContext,
435 cursor: Q42RangeTripleSelectCursor,
436 compressed: &mut [u8],
437 decoded: &mut [u8],
438 quin_scratch: &mut [NQuin],
439 out: &mut [BindingRow],
440) -> Result<Q42RangeTripleSelectPage, String> {
441 let source_page = execute_range_triple_page_into(
442 volume,
443 plan.subject,
444 plan.predicate,
445 plan.object,
446 None,
447 ctx,
448 &BindingRow::default(),
449 cursor.scan,
450 compressed,
451 decoded,
452 quin_scratch,
453 out,
454 )?;
455 let mut returned = 0usize;
456 let mut skipped = cursor.skipped;
457 let mut emitted = cursor.emitted;
458 for index in 0..source_page.returned {
459 let mut accepted = true;
460 for filter in plan.filters.iter().take(plan.filter_count as usize) {
461 if !matches!(
462 ExpressionEvaluator::evaluate(*filter, ctx, &out[index]),
463 Ok(EvalResult::Boolean(true))
464 ) {
465 accepted = false;
466 break;
467 }
468 }
469 if !accepted {
470 continue;
471 }
472 if skipped < plan.offset {
473 skipped += 1;
474 continue;
475 }
476 if emitted >= plan.limit {
477 return Ok(Q42RangeTripleSelectPage {
478 returned,
479 next_cursor: None,
480 });
481 }
482 let mut row = out[index];
483 if plan.projection_count != 0 {
484 let mut projected = BindingRow::default();
485 for variable in plan.projection.iter().take(plan.projection_count as usize) {
486 if let Some(value) = row.get(*variable) {
487 projected.set(*variable, value);
488 }
489 }
490 row = projected;
491 }
492 out[returned] = row;
493 returned += 1;
494 emitted += 1;
495 }
496 Ok(Q42RangeTripleSelectPage {
497 returned,
498 next_cursor: if emitted >= plan.limit {
499 None
500 } else {
501 source_page
502 .next_cursor
503 .map(|scan| Q42RangeTripleSelectCursor {
504 scan,
505 skipped,
506 emitted,
507 })
508 },
509 })
510}
511
512pub fn execute_range_volume_set_triple_select_page_into<S: crate::q42_volume::Q42RangeSource>(
515 volumes: &crate::q42_volume::Q42RangeVolumeSet<S>,
516 plan: Q42RangeTripleSelectPlan,
517 ctx: &SparqlQueryContext,
518 cursor: Q42RangeVolumeSetTripleSelectCursor,
519 compressed: &mut [u8],
520 decoded: &mut [u8],
521 quin_scratch: &mut [NQuin],
522 out: &mut [BindingRow],
523) -> Result<Q42RangeVolumeSetTripleSelectPage, String> {
524 let source_page = execute_range_volume_set_triple_page_into(
525 volumes,
526 plan.subject,
527 plan.predicate,
528 plan.object,
529 None,
530 ctx,
531 &BindingRow::default(),
532 cursor.scan,
533 compressed,
534 decoded,
535 quin_scratch,
536 out,
537 )?;
538 let mut returned = 0usize;
539 let mut skipped = cursor.skipped;
540 let mut emitted = cursor.emitted;
541 for index in 0..source_page.returned {
542 let mut accepted = true;
543 for filter in plan.filters.iter().take(plan.filter_count as usize) {
544 if !matches!(
545 ExpressionEvaluator::evaluate(*filter, ctx, &out[index]),
546 Ok(EvalResult::Boolean(true))
547 ) {
548 accepted = false;
549 break;
550 }
551 }
552 if !accepted {
553 continue;
554 }
555 if skipped < plan.offset {
556 skipped += 1;
557 continue;
558 }
559 if emitted >= plan.limit {
560 return Ok(Q42RangeVolumeSetTripleSelectPage {
561 returned,
562 next_cursor: None,
563 });
564 }
565 let mut row = out[index];
566 if plan.projection_count != 0 {
567 let mut projected = BindingRow::default();
568 for variable in plan.projection.iter().take(plan.projection_count as usize) {
569 if let Some(value) = row.get(*variable) {
570 projected.set(*variable, value);
571 }
572 }
573 row = projected;
574 }
575 out[returned] = row;
576 returned += 1;
577 emitted += 1;
578 }
579 Ok(Q42RangeVolumeSetTripleSelectPage {
580 returned,
581 next_cursor: if emitted >= plan.limit {
582 None
583 } else {
584 source_page
585 .next_cursor
586 .map(|scan| Q42RangeVolumeSetTripleSelectCursor {
587 scan,
588 skipped,
589 emitted,
590 })
591 },
592 })
593}
594
595pub fn execute_range_triple_page_into<S: crate::q42_volume::Q42RangeSource>(
600 volume: &crate::q42_volume::Q42RangeVolume<S>,
601 subject: u64,
602 predicate: u64,
603 object: u64,
604 context: Option<u64>,
605 ctx: &SparqlQueryContext,
606 input: &BindingRow,
607 cursor: Q42RangeSparqlCursor,
608 compressed: &mut [u8],
609 decoded: &mut [u8],
610 quin_scratch: &mut [NQuin],
611 out: &mut [BindingRow],
612) -> Result<Q42RangeSparqlPage, String> {
613 if quin_scratch.is_empty() || out.is_empty() {
614 return Err("range SPARQL page requires non-empty Quin and row buffers".to_string());
615 }
616 let bound = |term: u64| match term_is_var(term, ctx) {
617 Some(variable) => input.get(variable),
618 None => Some(term),
619 };
620 let pattern = crate::q42_volume::Q42RangeQueryPattern {
621 subject: bound(subject),
622 predicate: bound(predicate),
623 object: bound(object),
624 context,
625 };
626 let page = volume
627 .execute_query_page_into(
628 crate::q42_volume::Q42RangeQueryPlan::for_pattern(pattern),
629 cursor.scan,
630 compressed,
631 decoded,
632 quin_scratch,
633 )
634 .map_err(|error| format!("range Q42 triple scan: {error}"))?;
635 let mut returned = 0usize;
636 for quin in &quin_scratch[..page.returned] {
637 let mut row = *input;
638 if !bind_var(&mut row, subject, quin.subject, ctx)
639 || !bind_var(&mut row, predicate, quin.predicate, ctx)
640 || !bind_var(&mut row, object, quin.object, ctx)
641 {
642 continue;
643 }
644 if returned == out.len() {
645 return Err("range SPARQL row buffer is smaller than Quin scratch buffer".to_string());
646 }
647 out[returned] = row;
648 returned += 1;
649 }
650 Ok(Q42RangeSparqlPage {
651 returned,
652 next_cursor: page.next_cursor.map(|scan| Q42RangeSparqlCursor { scan }),
653 })
654}
655
656pub fn execute_range_volume_set_triple_page_into<S: crate::q42_volume::Q42RangeSource>(
660 volumes: &crate::q42_volume::Q42RangeVolumeSet<S>,
661 subject: u64,
662 predicate: u64,
663 object: u64,
664 context: Option<u64>,
665 ctx: &SparqlQueryContext,
666 input: &BindingRow,
667 cursor: Q42RangeVolumeSetSparqlCursor,
668 compressed: &mut [u8],
669 decoded: &mut [u8],
670 quin_scratch: &mut [NQuin],
671 out: &mut [BindingRow],
672) -> Result<Q42RangeVolumeSetSparqlPage, String> {
673 if quin_scratch.is_empty() || out.is_empty() {
674 return Err("range SPARQL page requires non-empty Quin and row buffers".to_string());
675 }
676 let bound = |term: u64| match term_is_var(term, ctx) {
677 Some(variable) => input.get(variable),
678 None => Some(term),
679 };
680 let page = volumes
681 .execute_query_page_into(
682 crate::q42_volume::Q42RangeQueryPlan::for_pattern(
683 crate::q42_volume::Q42RangeQueryPattern {
684 subject: bound(subject),
685 predicate: bound(predicate),
686 object: bound(object),
687 context,
688 },
689 ),
690 cursor.scan,
691 compressed,
692 decoded,
693 quin_scratch,
694 )
695 .map_err(|error| format!("range Q42 volume-set triple scan: {error}"))?;
696 let mut returned = 0usize;
697 for quin in &quin_scratch[..page.returned] {
698 let mut row = *input;
699 if !bind_var(&mut row, subject, quin.subject, ctx)
700 || !bind_var(&mut row, predicate, quin.predicate, ctx)
701 || !bind_var(&mut row, object, quin.object, ctx)
702 {
703 continue;
704 }
705 if returned == out.len() {
706 return Err("range SPARQL row buffer is smaller than Quin scratch buffer".to_string());
707 }
708 out[returned] = row;
709 returned += 1;
710 }
711 Ok(Q42RangeVolumeSetSparqlPage {
712 returned,
713 next_cursor: page
714 .next_cursor
715 .map(|scan| Q42RangeVolumeSetSparqlCursor { scan }),
716 })
717}
718
719} #[cfg(not(target_arch = "wasm32"))]
722pub use range_q42_exec::*;
723
724#[inline]
725fn term_is_var(term: u64, ctx: &SparqlQueryContext) -> Option<VariableId> {
726 let id = term as usize;
727 if id < ctx.variable_count {
728 Some(term as VariableId)
729 } else {
730 None
731 }
732}
733
734#[inline]
735fn bind_var(row: &mut BindingRow, term: u64, value: u64, ctx: &SparqlQueryContext) -> bool {
736 if let Some(var) = term_is_var(term, ctx) {
737 match row.get(var) {
738 Some(bound) if bound != value => false,
739 _ => {
740 row.set(var, value);
741 true
742 }
743 }
744 } else {
745 term == value
746 }
747}
748
749pub struct QueryExecutor<'a> {
751 pub quins: &'a [NQuin],
752 resolver: Option<crate::sparql_ast::TextResolver<'a>>,
756}
757
758impl<'a> QueryExecutor<'a> {
759 pub fn new(quins: &'a [NQuin]) -> Self {
760 Self {
761 quins,
762 resolver: None,
763 }
764 }
765
766 pub fn with_resolver(
768 quins: &'a [NQuin],
769 resolver: crate::sparql_ast::TextResolver<'a>,
770 ) -> Self {
771 Self {
772 quins,
773 resolver: Some(resolver),
774 }
775 }
776
777 pub fn execute(
779 &self,
780 plan: &ExecutionPlan,
781 ctx: &SparqlQueryContext,
782 ) -> Result<Vec<BindingRow>, String> {
783 if plan.operator_count == 0 {
784 return Err("Empty execution plan".to_string());
785 }
786 let mut results = Vec::new();
787 let mut row = BindingRow::new();
788
789 if self.execute_operator(plan.root_operator, plan, ctx, &mut row, &mut results)? {
790 return Ok(results);
791 }
792
793 Ok(results)
794 }
795
796 pub fn execute_ask(
798 &self,
799 plan: &ExecutionPlan,
800 ctx: &SparqlQueryContext,
801 ) -> Result<bool, String> {
802 let mut results = Vec::new();
803 let mut row = BindingRow::new();
804
805 self.execute_operator(plan.root_operator, plan, ctx, &mut row, &mut results)?;
806
807 Ok(!results.is_empty())
808 }
809
810 fn collect_template_triples(
816 pattern_id: PatternId,
817 ctx: &SparqlQueryContext,
818 out: &mut Vec<(u64, u64, u64)>,
819 ) {
820 match ctx.patterns.get(pattern_id as usize) {
821 Some(Pattern::Triple {
822 subject,
823 predicate,
824 object,
825 }) => out.push((*subject, *predicate, *object)),
826 Some(Pattern::Group { start_idx, len }) => {
827 for i in *start_idx..(*start_idx + *len) {
828 Self::collect_template_triples(i, ctx, out);
829 }
830 }
831 _ => {}
832 }
833 }
834
835 pub fn execute_construct(
844 &self,
845 plan: &ExecutionPlan,
846 ctx: &SparqlQueryContext,
847 template_pattern: PatternId,
848 ) -> Result<Vec<BindingRow>, String> {
849 let solutions = self.execute(plan, ctx)?;
850
851 let mut templates: Vec<(u64, u64, u64)> = Vec::new();
852 Self::collect_template_triples(template_pattern, ctx, &mut templates);
853
854 let resolve = |term: u64, row: &BindingRow| -> Option<u64> {
855 match term_is_var(term, ctx) {
856 Some(var) => row.get(var), None => Some(term), }
859 };
860
861 let mut seen = std::collections::HashSet::new();
862 let mut out = Vec::new();
863 for sol in &solutions {
864 for &(s, p, o) in &templates {
865 let (sv, pv, ov) = match (resolve(s, sol), resolve(p, sol), resolve(o, sol)) {
866 (Some(sv), Some(pv), Some(ov)) => (sv, pv, ov),
867 _ => continue,
868 };
869 if seen.insert((sv, pv, ov)) {
870 let mut row = BindingRow::new();
871 row.set(0, sv);
872 row.set(1, pv);
873 row.set(2, ov);
874 out.push(row);
875 }
876 }
877 }
878 Ok(out)
879 }
880
881 pub fn execute_describe(
888 &self,
889 plan: &ExecutionPlan,
890 ctx: &SparqlQueryContext,
891 describe: &DescribeQuery,
892 ) -> Result<Vec<BindingRow>, String> {
893 let solutions = if describe.root_pattern.is_some() {
896 self.execute(plan, ctx)?
897 } else {
898 Vec::new()
899 };
900
901 let mut resources: Vec<u64> = Vec::new();
902 let add = |r: u64, resources: &mut Vec<u64>| {
903 if !resources.contains(&r) {
904 resources.push(r);
905 }
906 };
907
908 if describe.var_count == 0 {
909 for sol in &solutions {
911 for var in 0..ctx.variable_count as VariableId {
912 if let Some(v) = sol.get(var) {
913 add(v, &mut resources);
914 }
915 }
916 }
917 } else {
918 for i in 0..describe.var_count as usize {
919 let term = describe.vars_or_ids[i];
920 match term_is_var(term, ctx) {
921 Some(var) => {
922 for sol in &solutions {
923 if let Some(v) = sol.get(var) {
924 add(v, &mut resources);
925 }
926 }
927 }
928 None => add(term, &mut resources),
929 }
930 }
931 }
932
933 let mut seen = std::collections::HashSet::new();
934 let mut out = Vec::new();
935 for &r in &resources {
936 for q in self.quins {
937 if q.subject == r && seen.insert((q.subject, q.predicate, q.object)) {
938 let mut row = BindingRow::new();
939 row.set(0, q.subject);
940 row.set(1, q.predicate);
941 row.set(2, q.object);
942 out.push(row);
943 }
944 }
945 }
946 Ok(out)
947 }
948
949 fn execute_operator(
950 &self,
951 op_id: OperatorId,
952 plan: &ExecutionPlan,
953 ctx: &SparqlQueryContext,
954 row: &mut BindingRow,
955 results: &mut Vec<BindingRow>,
956 ) -> Result<bool, String> {
957 let operator = plan
958 .operators
959 .get(op_id as usize)
960 .ok_or("Operator ID out of bounds")?;
961
962 match operator.operator_type {
963 PhysicalOperatorType::SubjectScan { subject } => {
964 self.execute_subject_scan(subject, ctx, row, results)
965 }
966 PhysicalOperatorType::PredicateScan { predicate } => {
967 self.execute_predicate_scan(predicate, ctx, row, results)
968 }
969 PhysicalOperatorType::ObjectScan { object } => {
970 self.execute_object_scan(object, ctx, row, results)
971 }
972 PhysicalOperatorType::TripleScan {
973 subject,
974 predicate,
975 object,
976 } => self.execute_triple_scan(subject, predicate, object, ctx, row, results),
977 PhysicalOperatorType::HashJoin {
978 left,
979 right,
980 join_var,
981 } => self.execute_hash_join(left, right, join_var, plan, ctx, row, results),
982 PhysicalOperatorType::NestedLoopJoin {
983 left,
984 right,
985 join_var,
986 } => self.execute_nested_loop_join(left, right, join_var, plan, ctx, row, results),
987 PhysicalOperatorType::Filter { input, expression } => {
988 self.execute_filter(input, expression, plan, ctx, row, results)
989 }
990 PhysicalOperatorType::Bind {
991 input,
992 var,
993 expression,
994 } => self.execute_bind(input, var, expression, plan, ctx, row, results),
995 PhysicalOperatorType::Project {
996 input,
997 vars,
998 var_count,
999 } => self.execute_project(input, vars, var_count, plan, ctx, row, results),
1000 PhysicalOperatorType::Limit {
1001 input,
1002 limit,
1003 offset,
1004 } => self.execute_limit(input, limit, offset, plan, ctx, row, results),
1005 PhysicalOperatorType::Sort {
1006 input,
1007 order_by,
1008 order_count,
1009 ascending,
1010 } => self.execute_sort(
1011 input,
1012 &order_by,
1013 order_count,
1014 &ascending,
1015 plan,
1016 ctx,
1017 row,
1018 results,
1019 ),
1020 PhysicalOperatorType::Union { left, right } => {
1021 self.execute_union(left, right, plan, ctx, row, results)
1022 }
1023 PhysicalOperatorType::Optional { left, right } => {
1024 self.execute_optional(left, right, plan, ctx, row, results)
1025 }
1026 PhysicalOperatorType::AntiJoin { left, right } => {
1027 self.execute_anti_join(left, right, plan, ctx, row, results)
1028 }
1029 PhysicalOperatorType::Distinct { input } => {
1030 self.execute_distinct(input, plan, ctx, row, results)
1031 }
1032 PhysicalOperatorType::SubSelect { query_id } => {
1033 self.execute_sub_select(query_id, ctx, row, results)
1034 }
1035 PhysicalOperatorType::GroupBy {
1036 input,
1037 group_vars,
1038 group_var_count,
1039 aggregates,
1040 aggregate_count,
1041 } => self.execute_group_by(
1042 input,
1043 group_vars,
1044 group_var_count,
1045 aggregates,
1046 aggregate_count,
1047 plan,
1048 ctx,
1049 row,
1050 results,
1051 ),
1052 PhysicalOperatorType::Having { input, expression } => {
1053 self.execute_having(input, expression, plan, ctx, row, results)
1054 }
1055 PhysicalOperatorType::PropertyPath {
1056 subject,
1057 path_id,
1058 object,
1059 } => self.execute_property_path(subject, path_id, object, ctx, row, results),
1060 PhysicalOperatorType::Graph {
1061 graph_var_or_id,
1062 inner,
1063 } => self.execute_graph(graph_var_or_id, inner, plan, ctx, row, results),
1064 PhysicalOperatorType::Service {
1065 endpoint_did_id,
1066 inner_pattern,
1067 } => self.execute_service(endpoint_did_id, inner_pattern, plan, ctx, row, results),
1068 PhysicalOperatorType::AsOf {
1069 input,
1070 timestamp_ms,
1071 mode,
1072 } => self.execute_as_of(input, timestamp_ms, mode, plan, ctx, row, results),
1073 PhysicalOperatorType::StarTripleScan {
1074 inner_subject,
1075 inner_predicate,
1076 inner_object,
1077 outer_predicate,
1078 outer_object,
1079 } => self.execute_star_triple_scan(
1080 inner_subject,
1081 inner_predicate,
1082 inner_object,
1083 outer_predicate,
1084 outer_object,
1085 ctx,
1086 row,
1087 results,
1088 ),
1089 }
1090 }
1091
1092 fn execute_subject_scan(
1093 &self,
1094 subject: u64,
1095 _ctx: &SparqlQueryContext,
1096 _row: &mut BindingRow,
1097 results: &mut Vec<BindingRow>,
1098 ) -> Result<bool, String> {
1099 for quin in self.quins {
1101 if quin.subject == subject {
1102 let mut new_row = BindingRow::new();
1105 new_row.slots[0] = Some(quin.subject);
1106 new_row.slots[1] = Some(quin.predicate);
1107 new_row.slots[2] = Some(quin.object);
1108 results.push(new_row);
1109 }
1110 }
1111 Ok(!results.is_empty())
1112 }
1113
1114 fn execute_predicate_scan(
1115 &self,
1116 predicate: u64,
1117 _ctx: &SparqlQueryContext,
1118 _row: &mut BindingRow,
1119 results: &mut Vec<BindingRow>,
1120 ) -> Result<bool, String> {
1121 for quin in self.quins {
1122 if quin.predicate == predicate {
1123 let mut new_row = BindingRow::new();
1124 new_row.slots[0] = Some(quin.subject);
1125 new_row.slots[1] = Some(quin.predicate);
1126 new_row.slots[2] = Some(quin.object);
1127 results.push(new_row);
1128 }
1129 }
1130 Ok(!results.is_empty())
1131 }
1132
1133 fn execute_object_scan(
1134 &self,
1135 object: u64,
1136 _ctx: &SparqlQueryContext,
1137 _row: &mut BindingRow,
1138 results: &mut Vec<BindingRow>,
1139 ) -> Result<bool, String> {
1140 for quin in self.quins {
1141 if quin.object == object {
1142 let mut new_row = BindingRow::new();
1143 new_row.slots[0] = Some(quin.subject);
1144 new_row.slots[1] = Some(quin.predicate);
1145 new_row.slots[2] = Some(quin.object);
1146 results.push(new_row);
1147 }
1148 }
1149 Ok(!results.is_empty())
1150 }
1151
1152 fn execute_triple_scan(
1153 &self,
1154 subject: u64,
1155 predicate: u64,
1156 object: u64,
1157 ctx: &SparqlQueryContext,
1158 row: &mut BindingRow,
1159 results: &mut Vec<BindingRow>,
1160 ) -> Result<bool, String> {
1161 for quin in self.quins {
1162 let mut candidate = *row;
1163 if !bind_var(&mut candidate, subject, quin.subject, ctx) {
1164 continue;
1165 }
1166 if !bind_var(&mut candidate, predicate, quin.predicate, ctx) {
1167 continue;
1168 }
1169 if !bind_var(&mut candidate, object, quin.object, ctx) {
1170 continue;
1171 }
1172 results.push(candidate);
1173 }
1174 Ok(!results.is_empty())
1175 }
1176
1177 fn execute_star_triple_scan(
1178 &self,
1179 inner_subject: u64,
1180 inner_predicate: u64,
1181 inner_object: u64,
1182 outer_predicate: u64,
1183 outer_object: u64,
1184 ctx: &SparqlQueryContext,
1185 row: &mut BindingRow,
1186 results: &mut Vec<BindingRow>,
1187 ) -> Result<bool, String> {
1188 for quin in self.quins {
1189 if !is_virtual_id(quin.subject) {
1190 continue;
1191 }
1192 let mut candidate = *row;
1193 if !bind_var(&mut candidate, outer_predicate, quin.predicate, ctx) {
1194 continue;
1195 }
1196 if !bind_var(&mut candidate, outer_object, quin.object, ctx) {
1197 continue;
1198 }
1199
1200 if let (Some(s), Some(p), Some(o)) = (
1201 term_is_var(inner_subject, ctx),
1202 term_is_var(inner_predicate, ctx),
1203 term_is_var(inner_object, ctx),
1204 ) {
1205 if let Some(components) = self.lookup_star_components(quin.subject) {
1206 candidate.set(s, components[0]);
1207 candidate.set(p, components[1]);
1208 candidate.set(o, components[2]);
1209 results.push(candidate);
1210 }
1211 } else {
1212 let expected_vid = generate_embedded_triple_id(
1213 if term_is_var(inner_subject, ctx).is_some() {
1214 0
1215 } else {
1216 inner_subject
1217 },
1218 if term_is_var(inner_predicate, ctx).is_some() {
1219 0
1220 } else {
1221 inner_predicate
1222 },
1223 if term_is_var(inner_object, ctx).is_some() {
1224 0
1225 } else {
1226 inner_object
1227 },
1228 );
1229 if quin.subject == expected_vid
1230 && bind_var(&mut candidate, outer_predicate, quin.predicate, ctx)
1231 && bind_var(&mut candidate, outer_object, quin.object, ctx)
1232 {
1233 results.push(candidate);
1234 }
1235 }
1236 }
1237 Ok(!results.is_empty())
1238 }
1239
1240 fn lookup_star_components(&self, virtual_id: u64) -> Option<[u64; 3]> {
1241 for quin in self.quins {
1242 let candidate = generate_embedded_triple_id(quin.subject, quin.predicate, quin.object);
1243 if candidate == virtual_id {
1244 return Some([quin.subject, quin.predicate, quin.object]);
1245 }
1246 }
1247 None
1248 }
1249
1250 fn execute_hash_join(
1251 &self,
1252 left: OperatorId,
1253 right: OperatorId,
1254 join_var: VariableId,
1255 plan: &ExecutionPlan,
1256 ctx: &SparqlQueryContext,
1257 _row: &mut BindingRow,
1258 results: &mut Vec<BindingRow>,
1259 ) -> Result<bool, String> {
1260 let mut left_results = Vec::new();
1261 let mut left_row = BindingRow::new();
1262 self.execute_operator(left, plan, ctx, &mut left_row, &mut left_results)?;
1263
1264 let mut right_results = Vec::new();
1265 let mut right_row = BindingRow::new();
1266 self.execute_operator(right, plan, ctx, &mut right_row, &mut right_results)?;
1267
1268 left_results
1270 .sort_unstable_by(|a, b| a.slots[join_var as usize].cmp(&b.slots[join_var as usize]));
1271 right_results
1272 .sort_unstable_by(|a, b| a.slots[join_var as usize].cmp(&b.slots[join_var as usize]));
1273
1274 let mut i = 0;
1275 let mut j = 0;
1276
1277 while i < left_results.len() && j < right_results.len() {
1278 let left_val = left_results[i].slots[join_var as usize];
1279 let right_val = right_results[j].slots[join_var as usize];
1280
1281 if left_val < right_val {
1286 i += 1;
1287 } else if left_val > right_val {
1288 j += 1;
1289 } else {
1290 let mut left_end = i + 1;
1291 while left_end < left_results.len()
1292 && left_results[left_end].slots[join_var as usize] == left_val
1293 {
1294 left_end += 1;
1295 }
1296
1297 let mut right_end = j + 1;
1298 while right_end < right_results.len()
1299 && right_results[right_end].slots[join_var as usize] == right_val
1300 {
1301 right_end += 1;
1302 }
1303
1304 for l in &left_results[i..left_end] {
1305 for r in &right_results[j..right_end] {
1306 let mut compatible = true;
1307 for k in 0..MAX_BINDINGS {
1308 if let (Some(a), Some(b)) = (l.slots[k], r.slots[k]) {
1309 if a != b {
1310 compatible = false;
1311 break;
1312 }
1313 }
1314 }
1315
1316 if compatible {
1317 let mut joined = BindingRow::new();
1318 for k in 0..MAX_BINDINGS {
1319 joined.slots[k] = l.slots[k].or(r.slots[k]);
1320 }
1321 results.push(joined);
1322 }
1323 }
1324 }
1325
1326 i = left_end;
1327 j = right_end;
1328 }
1329 }
1330
1331 Ok(!results.is_empty())
1332 }
1333
1334 fn execute_nested_loop_join(
1335 &self,
1336 left: OperatorId,
1337 right: OperatorId,
1338 _join_var: VariableId,
1339 plan: &ExecutionPlan,
1340 ctx: &SparqlQueryContext,
1341 _row: &mut BindingRow,
1342 results: &mut Vec<BindingRow>,
1343 ) -> Result<bool, String> {
1344 let mut left_results = Vec::new();
1345 let mut left_row = BindingRow::new();
1346 self.execute_operator(left, plan, ctx, &mut left_row, &mut left_results)?;
1347
1348 let mut right_results = Vec::new();
1349 let mut right_row = BindingRow::new();
1350 self.execute_operator(right, plan, ctx, &mut right_row, &mut right_results)?;
1351
1352 for l in &left_results {
1354 for r in &right_results {
1355 let mut compatible = true;
1356 for i in 0..MAX_BINDINGS {
1357 if let (Some(a), Some(b)) = (l.slots[i], r.slots[i]) {
1358 if a != b {
1359 compatible = false;
1360 break;
1361 }
1362 }
1363 }
1364
1365 if compatible {
1366 let mut joined = BindingRow::new();
1367 for i in 0..MAX_BINDINGS {
1368 joined.slots[i] = l.slots[i].or(r.slots[i]);
1369 }
1370 results.push(joined);
1371 }
1372 }
1373 }
1374
1375 Ok(!results.is_empty())
1376 }
1377
1378 fn execute_filter(
1379 &self,
1380 input: OperatorId,
1381 expression: ExpressionId,
1382 plan: &ExecutionPlan,
1383 ctx: &SparqlQueryContext,
1384 row: &mut BindingRow,
1385 results: &mut Vec<BindingRow>,
1386 ) -> Result<bool, String> {
1387 let mut input_results = Vec::new();
1388 self.execute_operator(input, plan, ctx, row, &mut input_results)?;
1389
1390 for input_row in input_results {
1394 if self.eval_filter_bool(expression, ctx, &input_row)? {
1395 results.push(input_row);
1396 }
1397 }
1398
1399 Ok(!results.is_empty())
1400 }
1401
1402 fn expr_contains_exists(expr_id: ExpressionId, ctx: &SparqlQueryContext) -> bool {
1407 match ctx.expressions.get(expr_id as usize) {
1408 Some(Expression::Exists { .. }) => true,
1409 Some(Expression::UnaryOp { expr, .. }) => Self::expr_contains_exists(*expr, ctx),
1410 Some(Expression::BinaryOp { left, right, .. }) => {
1411 Self::expr_contains_exists(*left, ctx) || Self::expr_contains_exists(*right, ctx)
1412 }
1413 _ => false,
1414 }
1415 }
1416
1417 fn eval_exists(
1421 &self,
1422 pattern: PatternId,
1423 ctx: &SparqlQueryContext,
1424 row: &BindingRow,
1425 ) -> Result<bool, String> {
1426 let mut sub_plan = ExecutionPlan::new();
1427 let op = QueryPlanner::plan_pattern(pattern, ctx, &mut sub_plan)?;
1428 sub_plan.root_operator = op;
1429 let mut seed = *row;
1430 let mut local = Vec::new();
1431 self.execute_operator(op, &sub_plan, ctx, &mut seed, &mut local)?;
1432 Ok(!local.is_empty())
1433 }
1434
1435 fn eval_filter_bool(
1439 &self,
1440 expr_id: ExpressionId,
1441 ctx: &SparqlQueryContext,
1442 row: &BindingRow,
1443 ) -> Result<bool, String> {
1444 let expr = *ctx
1445 .expressions
1446 .get(expr_id as usize)
1447 .ok_or("Expression ID out of bounds")?;
1448 match expr {
1449 Expression::Exists { pattern, negated } => {
1450 Ok(self.eval_exists(pattern, ctx, row)? ^ negated)
1451 }
1452 Expression::UnaryOp {
1453 op: UnaryOp::Not,
1454 expr,
1455 } if Self::expr_contains_exists(expr, ctx) => {
1456 Ok(!self.eval_filter_bool(expr, ctx, row)?)
1457 }
1458 Expression::BinaryOp {
1459 op: BinaryOp::And,
1460 left,
1461 right,
1462 } if Self::expr_contains_exists(left, ctx)
1463 || Self::expr_contains_exists(right, ctx) =>
1464 {
1465 Ok(self.eval_filter_bool(left, ctx, row)?
1466 && self.eval_filter_bool(right, ctx, row)?)
1467 }
1468 Expression::BinaryOp {
1469 op: BinaryOp::Or,
1470 left,
1471 right,
1472 } if Self::expr_contains_exists(left, ctx)
1473 || Self::expr_contains_exists(right, ctx) =>
1474 {
1475 Ok(self.eval_filter_bool(left, ctx, row)?
1476 || self.eval_filter_bool(right, ctx, row)?)
1477 }
1478 _ => {
1479 let r =
1480 ExpressionEvaluator::evaluate_with_resolver(expr_id, ctx, row, self.resolver)?;
1481 Ok(r.as_bool())
1482 }
1483 }
1484 }
1485
1486 fn execute_bind(
1487 &self,
1488 input: OperatorId,
1489 var: VariableId,
1490 expression: ExpressionId,
1491 plan: &ExecutionPlan,
1492 ctx: &SparqlQueryContext,
1493 row: &mut BindingRow,
1494 results: &mut Vec<BindingRow>,
1495 ) -> Result<bool, String> {
1496 let mut input_results = Vec::new();
1497 self.execute_operator(input, plan, ctx, row, &mut input_results)?;
1498
1499 for mut input_row in input_results {
1500 match ExpressionEvaluator::evaluate_with_resolver(
1507 expression,
1508 ctx,
1509 &input_row,
1510 self.resolver,
1511 ) {
1512 Ok(EvalResult::Numeric(n)) | Ok(EvalResult::Iri(n)) | Ok(EvalResult::String(n)) => {
1513 input_row.set(var, n);
1514 }
1515 Ok(EvalResult::Boolean(b)) => {
1516 input_row.set(var, b as u64);
1517 }
1518 Ok(EvalResult::Float(f)) => {
1519 input_row.set(var, f.to_bits());
1521 }
1522 Err(_) => { }
1523 }
1524 results.push(input_row);
1525 }
1526
1527 Ok(!results.is_empty())
1528 }
1529
1530 fn execute_project(
1531 &self,
1532 input: OperatorId,
1533 vars: [VariableId; MAX_VARIABLES],
1534 var_count: u8,
1535 plan: &ExecutionPlan,
1536 ctx: &SparqlQueryContext,
1537 row: &mut BindingRow,
1538 results: &mut Vec<BindingRow>,
1539 ) -> Result<bool, String> {
1540 let mut input_results = Vec::new();
1547 self.execute_operator(input, plan, ctx, row, &mut input_results)?;
1548 for in_row in input_results {
1549 let mut projected = BindingRow::new();
1550 for &v in vars.iter().take(var_count as usize) {
1551 if let Some(val) = in_row.get(v) {
1552 projected.set(v, val);
1553 }
1554 }
1555 results.push(projected);
1556 }
1557 Ok(!results.is_empty())
1558 }
1559
1560 fn execute_limit(
1561 &self,
1562 input: OperatorId,
1563 limit: u64,
1564 offset: u64,
1565 plan: &ExecutionPlan,
1566 ctx: &SparqlQueryContext,
1567 row: &mut BindingRow,
1568 results: &mut Vec<BindingRow>,
1569 ) -> Result<bool, String> {
1570 let mut all_results = Vec::new();
1571 self.execute_operator(input, plan, ctx, row, &mut all_results)?;
1572
1573 let start = offset as usize;
1575 let end = if limit == u64::MAX {
1576 all_results.len()
1577 } else {
1578 (start + limit as usize).min(all_results.len())
1579 };
1580
1581 if start < all_results.len() {
1582 results.extend_from_slice(&all_results[start..end]);
1583 }
1584
1585 Ok(!results.is_empty())
1586 }
1587
1588 fn execute_sort(
1589 &self,
1590 input: OperatorId,
1591 order_by: &[ExpressionId; MAX_ORDER_CONDITIONS],
1592 order_count: u8,
1593 ascending: &[bool; MAX_ORDER_CONDITIONS],
1594 plan: &ExecutionPlan,
1595 ctx: &SparqlQueryContext,
1596 row: &mut BindingRow,
1597 results: &mut Vec<BindingRow>,
1598 ) -> Result<bool, String> {
1599 let start_len = results.len();
1600 self.execute_operator(input, plan, ctx, row, results)?;
1601
1602 let slice = &mut results[start_len..];
1604
1605 slice.sort_unstable_by(|a, b| {
1606 for i in 0..order_count as usize {
1607 let expr = order_by[i];
1608 let asc = ascending[i];
1609
1610 let val_a =
1611 ExpressionEvaluator::evaluate_with_resolver(expr, ctx, a, self.resolver)
1612 .unwrap_or(crate::sparql_filter::EvalResult::Numeric(0));
1613 let val_b =
1614 ExpressionEvaluator::evaluate_with_resolver(expr, ctx, b, self.resolver)
1615 .unwrap_or(crate::sparql_filter::EvalResult::Numeric(0));
1616
1617 let cmp = val_a.total_cmp(&val_b);
1618 if cmp != std::cmp::Ordering::Equal {
1619 return if asc { cmp } else { cmp.reverse() };
1620 }
1621 }
1622 std::cmp::Ordering::Equal
1623 });
1624
1625 Ok(!results.is_empty())
1626 }
1627
1628 fn execute_union(
1629 &self,
1630 left: OperatorId,
1631 right: OperatorId,
1632 plan: &ExecutionPlan,
1633 ctx: &SparqlQueryContext,
1634 row: &mut BindingRow,
1635 results: &mut Vec<BindingRow>,
1636 ) -> Result<bool, String> {
1637 let _start_len = results.len();
1638
1639 self.execute_operator(left, plan, ctx, row, results)?;
1641
1642 self.execute_operator(right, plan, ctx, row, results)?;
1644
1645 Ok(!results.is_empty())
1648 }
1649
1650 fn execute_optional(
1651 &self,
1652 left: OperatorId,
1653 right: OperatorId,
1654 plan: &ExecutionPlan,
1655 ctx: &SparqlQueryContext,
1656 row: &mut BindingRow,
1657 results: &mut Vec<BindingRow>,
1658 ) -> Result<bool, String> {
1659 let mut left_results = Vec::new();
1661 let mut right_results = Vec::new();
1662 self.execute_operator(left, plan, ctx, row, &mut left_results)?;
1663
1664 for left_result in left_results {
1666 right_results.clear();
1667 let mut right_row = left_result; let right_matched =
1669 self.execute_operator(right, plan, ctx, &mut right_row, &mut right_results)?;
1670
1671 if right_matched && !right_results.is_empty() {
1672 results.extend_from_slice(&right_results);
1674 } else {
1675 results.push(left_result);
1677 }
1678 }
1679
1680 Ok(!results.is_empty())
1681 }
1682
1683 fn execute_anti_join(
1689 &self,
1690 left: OperatorId,
1691 right: OperatorId,
1692 plan: &ExecutionPlan,
1693 ctx: &SparqlQueryContext,
1694 row: &mut BindingRow,
1695 results: &mut Vec<BindingRow>,
1696 ) -> Result<bool, String> {
1697 let mut left_results = Vec::new();
1698 self.execute_operator(left, plan, ctx, row, &mut left_results)?;
1699
1700 let mut right_results = Vec::new();
1702 let mut right_row = BindingRow::new();
1703 self.execute_operator(right, plan, ctx, &mut right_row, &mut right_results)?;
1704
1705 for l in left_results {
1706 let excluded = right_results.iter().any(|r| {
1707 let mut compatible = true;
1708 let mut shares = false;
1709 for k in 0..MAX_BINDINGS {
1710 if let (Some(a), Some(b)) = (l.slots[k], r.slots[k]) {
1711 shares = true;
1712 if a != b {
1713 compatible = false;
1714 break;
1715 }
1716 }
1717 }
1718 compatible && shares
1719 });
1720 if !excluded {
1721 results.push(l);
1722 }
1723 }
1724
1725 Ok(!results.is_empty())
1726 }
1727
1728 fn execute_sub_select(
1734 &self,
1735 query_id: u16,
1736 ctx: &SparqlQueryContext,
1737 row: &BindingRow,
1738 results: &mut Vec<BindingRow>,
1739 ) -> Result<bool, String> {
1740 let subquery = *ctx
1741 .subqueries
1742 .get(query_id as usize)
1743 .ok_or("Subquery ID out of bounds")?;
1744 let sub_plan = QueryPlanner::plan(&subquery, ctx)?;
1745 let sub_rows = self.execute(&sub_plan, ctx)?;
1746
1747 for sub_row in sub_rows {
1748 let mut merged = *row;
1749 let mut compatible = true;
1750 for var in 0..ctx.variable_count as VariableId {
1751 if let Some(v) = sub_row.get(var) {
1752 match merged.get(var) {
1753 Some(existing) if existing != v => {
1754 compatible = false;
1755 break;
1756 }
1757 _ => merged.set(var, v),
1758 }
1759 }
1760 }
1761 if compatible {
1762 results.push(merged);
1763 }
1764 }
1765 Ok(!results.is_empty())
1766 }
1767
1768 fn execute_distinct(
1769 &self,
1770 input: OperatorId,
1771 plan: &ExecutionPlan,
1772 ctx: &SparqlQueryContext,
1773 row: &mut BindingRow,
1774 results: &mut Vec<BindingRow>,
1775 ) -> Result<bool, String> {
1776 let start_len = results.len();
1777 self.execute_operator(input, plan, ctx, row, results)?;
1778
1779 let slice = &mut results[start_len..];
1780 slice.sort_unstable();
1781
1782 if results.len() > start_len {
1783 let mut write_idx = start_len + 1;
1784 for read_idx in (start_len + 1)..results.len() {
1785 if results[read_idx] != results[write_idx - 1] {
1786 results[write_idx] = results[read_idx];
1787 write_idx += 1;
1788 }
1789 }
1790 results.truncate(write_idx);
1791 }
1792
1793 Ok(!results.is_empty())
1794 }
1795
1796 fn execute_group_by(
1797 &self,
1798 input: OperatorId,
1799 group_vars: [VariableId; MAX_VARIABLES],
1800 group_var_count: u8,
1801 aggregates: [crate::sparql_planner::AggregateSpec; 16],
1802 aggregate_count: u8,
1803 plan: &ExecutionPlan,
1804 ctx: &SparqlQueryContext,
1805 row: &mut BindingRow,
1806 results: &mut Vec<BindingRow>,
1807 ) -> Result<bool, String> {
1808 let mut all_results = Vec::new();
1809 self.execute_operator(input, plan, ctx, row, &mut all_results)?;
1810
1811 let mut agg_ctx = AggregationContext::new(&aggregates, aggregate_count);
1813
1814 for result in &all_results {
1815 let mut key = GroupKey::new();
1816 for i in 0..group_var_count as usize {
1817 let var_id = group_vars[i];
1818 if let Some(value) = result.get(var_id) {
1819 key.set(var_id, value);
1820 }
1821 }
1822
1823 let group_idx = agg_ctx.find_or_create_group(key)?;
1824 agg_ctx.add_values_to_group(group_idx, result);
1825 }
1826
1827 for i in 0..agg_ctx.group_count as usize {
1829 let (key, accumulators) = &agg_ctx.groups[i];
1830 let mut result_row = BindingRow::new();
1831 for j in 0..key.var_count as usize {
1832 result_row.slots[j] = Some(key.values[j]);
1833 }
1834
1835 for j in 0..aggregate_count as usize {
1837 if let Some(result_val) = accumulators[j].get_result() {
1838 let out_var = aggregates[j].output_var;
1839 result_row.slots[out_var as usize] = Some(result_val);
1840 }
1841 }
1842 results.push(result_row);
1843 }
1844
1845 Ok(!results.is_empty())
1846 }
1847
1848 fn execute_having(
1849 &self,
1850 input: OperatorId,
1851 expression: ExpressionId,
1852 plan: &ExecutionPlan,
1853 ctx: &SparqlQueryContext,
1854 row: &mut BindingRow,
1855 results: &mut Vec<BindingRow>,
1856 ) -> Result<bool, String> {
1857 let mut all_results = Vec::new();
1858 self.execute_operator(input, plan, ctx, row, &mut all_results)?;
1859
1860 for result in all_results {
1862 if self.eval_filter_bool(expression, ctx, &result)? {
1863 results.push(result);
1864 }
1865 }
1866
1867 Ok(!results.is_empty())
1868 }
1869
1870 fn execute_property_path(
1871 &self,
1872 subject: u64,
1873 path_id: PathId,
1874 object: u64,
1875 ctx: &SparqlQueryContext,
1876 row: &mut BindingRow,
1877 results: &mut Vec<BindingRow>,
1878 ) -> Result<bool, String> {
1879 let path = ctx
1880 .paths
1881 .get(path_id as usize)
1882 .ok_or("Path ID out of bounds")?;
1883
1884 match path {
1885 crate::sparql_ast::Path::Predicate(pred) => {
1886 self.execute_triple_scan(subject, *pred, object, ctx, row, results)
1888 }
1889 crate::sparql_ast::Path::Inverse(inner_id) => {
1890 self.execute_property_path(object, *inner_id, subject, ctx, row, results)
1892 }
1893 crate::sparql_ast::Path::Sequence { left, right } => {
1894 let mut intermediate_results = Vec::new();
1896 self.execute_property_path(subject, *left, 0, ctx, row, &mut intermediate_results)?;
1897
1898 for inter_result in intermediate_results {
1899 let intermediate_val = inter_result.slots[0].unwrap_or(0);
1900 self.execute_property_path(
1901 intermediate_val,
1902 *right,
1903 object,
1904 ctx,
1905 row,
1906 results,
1907 )?;
1908 }
1909 Ok(!results.is_empty())
1910 }
1911 crate::sparql_ast::Path::Alternative { left, right } => {
1912 let mut left_results = Vec::new();
1914 let mut right_results = Vec::new();
1915
1916 self.execute_property_path(subject, *left, object, ctx, row, &mut left_results)?;
1917 self.execute_property_path(subject, *right, object, ctx, row, &mut right_results)?;
1918
1919 results.extend_from_slice(&left_results);
1920 results.extend_from_slice(&right_results);
1921 Ok(!results.is_empty())
1922 }
1923 crate::sparql_ast::Path::ZeroOrMore(inner_id) => {
1924 let mut reached = self.path_transitive_hops(subject, *inner_id, ctx, row)?;
1928 reached.insert(subject);
1929 Self::emit_path_nodes(reached, object, results);
1930 Ok(!results.is_empty())
1931 }
1932 crate::sparql_ast::Path::OneOrMore(inner_id) => {
1933 let reached = self.path_transitive_hops(subject, *inner_id, ctx, row)?;
1937 Self::emit_path_nodes(reached, object, results);
1938 Ok(!results.is_empty())
1939 }
1940 crate::sparql_ast::Path::ZeroOrOne(inner_id) => {
1941 if subject == object {
1944 let mut direct_row = BindingRow::new();
1945 direct_row.slots[0] = Some(subject);
1946 results.push(direct_row);
1947 }
1948
1949 self.execute_property_path(subject, *inner_id, object, ctx, row, results)
1951 }
1952 }
1953 }
1954
1955 fn path_transitive_hops(
1960 &self,
1961 subject: u64,
1962 path_id: PathId,
1963 ctx: &SparqlQueryContext,
1964 row: &mut BindingRow,
1965 ) -> Result<std::collections::HashSet<u64>, String> {
1966 use std::collections::HashSet;
1967 let mut reached: HashSet<u64> = HashSet::new();
1968 let mut expanded: HashSet<u64> = HashSet::new();
1969 let mut frontier: Vec<u64> = vec![subject];
1970 while let Some(node) = frontier.pop() {
1971 if !expanded.insert(node) {
1972 continue; }
1974 let mut hops = Vec::new();
1975 self.execute_property_path(node, path_id, 0, ctx, row, &mut hops)?;
1976 for hop in hops {
1977 let next = hop.slots[0].unwrap_or(0);
1978 reached.insert(next);
1979 frontier.push(next);
1980 }
1981 }
1982 Ok(reached)
1983 }
1984
1985 fn emit_path_nodes(
1988 nodes: impl IntoIterator<Item = u64>,
1989 object: u64,
1990 results: &mut Vec<BindingRow>,
1991 ) {
1992 for node in nodes {
1993 if object == 0 || node == object {
1994 let mut r = BindingRow::new();
1995 r.slots[0] = Some(node);
1996 results.push(r);
1997 }
1998 }
1999 }
2000
2001 fn execute_graph(
2002 &self,
2003 graph_var_or_id: u64,
2004 inner: OperatorId,
2005 plan: &ExecutionPlan,
2006 ctx: &SparqlQueryContext,
2007 row: &mut BindingRow,
2008 results: &mut Vec<BindingRow>,
2009 ) -> Result<bool, String> {
2010 if let Some(graph_var) = term_is_var(graph_var_or_id, ctx) {
2014 let mut contexts: Vec<u64> = Vec::new();
2015 for q in self.quins {
2016 if q.context != 0 && !contexts.contains(&q.context) {
2017 contexts.push(q.context);
2018 }
2019 }
2020
2021 let mut matched = false;
2022 for gctx in contexts {
2023 let graph_quins = crate::query_engine::filter_by_context(self.quins, gctx);
2024 if graph_quins.is_empty() {
2025 continue;
2026 }
2027 let temp_executor = QueryExecutor {
2028 quins: &graph_quins,
2029 resolver: self.resolver,
2030 };
2031 let mut seed = *row;
2034 seed.set(graph_var, gctx);
2035 let mut local = Vec::new();
2036 if temp_executor.execute_operator(inner, plan, ctx, &mut seed, &mut local)? {
2037 matched = true;
2038 }
2039 for mut r in local {
2040 r.set(graph_var, gctx);
2041 results.push(r);
2042 }
2043 }
2044 return Ok(matched);
2045 }
2046
2047 let graph_id = graph_var_or_id;
2049
2050 let graph_quins = crate::query_engine::filter_by_context(self.quins, graph_id);
2052
2053 if graph_quins.is_empty() {
2054 return Ok(false);
2055 }
2056
2057 let temp_executor = QueryExecutor {
2060 quins: &graph_quins,
2061 resolver: self.resolver,
2062 };
2063
2064 temp_executor.execute_operator(inner, plan, ctx, row, results)
2066 }
2067
2068 fn execute_service(
2069 &self,
2070 endpoint_did_id: u64,
2071 inner: OperatorId,
2072 plan: &ExecutionPlan,
2073 ctx: &SparqlQueryContext,
2074 row: &mut BindingRow,
2075 results: &mut Vec<BindingRow>,
2076 ) -> Result<bool, String> {
2077 let _network_buffer = [0u8; 4096];
2080
2081 let is_did = (endpoint_did_id & 0x8000000000000000) != 0;
2083
2084 if !is_did {
2085 return Err("Invalid DID: missing 0x8 prefix".to_string());
2086 }
2087
2088 self.execute_operator(inner, plan, ctx, row, results)
2099 }
2100
2101 fn execute_as_of(
2102 &self,
2103 input: OperatorId,
2104 timestamp_ms: u64,
2105 mode: TemporalMode,
2106 plan: &ExecutionPlan,
2107 ctx: &SparqlQueryContext,
2108 row: &mut BindingRow,
2109 results: &mut Vec<BindingRow>,
2110 ) -> Result<bool, String> {
2111 let mut inner_results = Vec::new();
2112 self.execute_operator(input, plan, ctx, row, &mut inner_results)?;
2113
2114 for candidate in inner_results {
2115 let subject_opt = candidate.slots.iter().find_map(|s| *s);
2116 let passes = if let Some(subject) = subject_opt {
2117 self.check_temporal_constraint(subject, timestamp_ms, mode)
2118 } else {
2119 true
2120 };
2121 if passes {
2122 results.push(candidate);
2123 }
2124 }
2125 Ok(!results.is_empty())
2126 }
2127
2128 fn check_temporal_constraint(
2133 &self,
2134 subject: u64,
2135 timestamp_ms: u64,
2136 mode: TemporalMode,
2137 ) -> bool {
2138 use crate::kml_bridge::T_CONTEXT;
2139 use crate::sparql_filter::prov_predicates;
2140
2141 match mode {
2142 TemporalMode::AsOf => {
2143 let gen_time = self
2144 .quins
2145 .iter()
2146 .find(|q| {
2147 q.subject == subject
2148 && q.predicate == prov_predicates::GENERATED_AT_TIME
2149 && q.context == T_CONTEXT
2150 })
2151 .map(|q| q.object);
2152 gen_time.map(|t| t <= timestamp_ms).unwrap_or(true)
2153 }
2154 TemporalMode::AtTime => {
2155 let start = self
2156 .quins
2157 .iter()
2158 .find(|q| {
2159 q.subject == subject
2160 && q.predicate == prov_predicates::STARTED_AT_TIME
2161 && q.context == T_CONTEXT
2162 })
2163 .map(|q| q.object);
2164 let end = self
2165 .quins
2166 .iter()
2167 .find(|q| {
2168 q.subject == subject
2169 && q.predicate == prov_predicates::ENDED_AT_TIME
2170 && q.context == T_CONTEXT
2171 })
2172 .map(|q| q.object);
2173 start.map(|t| t <= timestamp_ms).unwrap_or(true)
2174 && end.map(|t| timestamp_ms <= t).unwrap_or(true)
2175 }
2176 }
2177 }
2178}
2179
2180pub fn unpack_virtual_triple(
2182 virtual_id: u64,
2183 lexicon: &crate::q42_lex::Q42LexMmap<'_>,
2184 row: &mut crate::sparql_ast::BindingRow,
2185 s_var_idx: Option<u8>,
2186 p_var_idx: Option<u8>,
2187 o_var_idx: Option<u8>,
2188) -> Result<(), String> {
2189 if let Some([s_id, p_id, o_id]) = lexicon.lookup_embedded_triple(virtual_id) {
2190 if let Some(s_idx) = s_var_idx {
2191 row.slots[s_idx as usize] = Some(s_id);
2192 }
2193 if let Some(p_idx) = p_var_idx {
2194 row.slots[p_idx as usize] = Some(p_id);
2195 }
2196 if let Some(o_idx) = o_var_idx {
2197 row.slots[o_idx as usize] = Some(o_id);
2198 }
2199 Ok(())
2200 } else {
2201 Err("Virtual ID not found in lexicon or invalid".to_string())
2202 }
2203}
2204
2205#[cfg(test)]
2206mod tests {
2207 use super::*;
2208
2209 #[test]
2210 fn test_executor_creation() {
2211 let quins = vec![];
2212 let executor = QueryExecutor::new(&quins);
2213 assert_eq!(executor.quins.len(), 0);
2214 }
2215
2216 #[test]
2217 fn test_execute_empty_plan() {
2218 let quins = vec![];
2219 let executor = QueryExecutor::new(&quins);
2220 let plan = ExecutionPlan::new();
2221 let ctx = SparqlQueryContext::new();
2222
2223 let result = executor.execute(&plan, &ctx);
2224 assert!(result.is_err());
2226 }
2227
2228 #[cfg(not(target_arch = "wasm32"))]
2229 #[test]
2230 fn range_triple_page_uses_q42_bidx_without_resident_graph() {
2231 let dir = tempfile::TempDir::new().unwrap();
2232 let path = dir.path().join("range.q42");
2233 let quin = NQuin {
2234 subject: 101,
2235 predicate: 202,
2236 object: 303,
2237 context: 0,
2238 metadata: 0,
2239 parity: 0,
2240 };
2241 crate::q42_volume::write_unified_volume(
2242 &path,
2243 &std::collections::HashMap::new(),
2244 &[(quin.object, quin.object)],
2245 &[vec![quin]],
2246 )
2247 .unwrap();
2248 let source = crate::q42_volume::LocalFileRangeSource::open(&path).unwrap();
2249 let volume = crate::q42_volume::Q42RangeVolume::open(source).unwrap();
2250 let context = SparqlQueryContext::new();
2251 let mut compressed = [0u8; crate::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
2252 let mut decoded = [0u8; crate::q42_volume::SUPERBLOCK_SIZE];
2253 let mut quins = [NQuin::default(); 1];
2254 let mut rows = [BindingRow::default(); 1];
2255 let page = execute_range_triple_page_into(
2256 &volume,
2257 quin.subject,
2258 quin.predicate,
2259 quin.object,
2260 None,
2261 &context,
2262 &BindingRow::default(),
2263 Q42RangeSparqlCursor::default(),
2264 &mut compressed,
2265 &mut decoded,
2266 &mut quins,
2267 &mut rows,
2268 )
2269 .unwrap();
2270 assert_eq!(page.returned, 1);
2271 assert_eq!(rows[0].slots[0], None);
2272 }
2273
2274 #[cfg(not(target_arch = "wasm32"))]
2275 #[test]
2276 fn range_nested_loop_join_resumes_without_a_resident_graph() {
2277 let dir = tempfile::TempDir::new().unwrap();
2278 let path = dir.path().join("range-join.q42");
2279 let left = NQuin {
2280 subject: 10,
2281 predicate: 20,
2282 object: 30,
2283 context: 0,
2284 metadata: 0,
2285 parity: 0,
2286 };
2287 let right = NQuin {
2288 subject: 30,
2289 predicate: 40,
2290 object: 50,
2291 context: 0,
2292 metadata: 0,
2293 parity: 0,
2294 };
2295 crate::q42_volume::write_unified_volume(
2296 &path,
2297 &std::collections::HashMap::new(),
2298 &[(left.object, right.object)],
2299 &[vec![left, right]],
2300 )
2301 .unwrap();
2302 let source = crate::q42_volume::LocalFileRangeSource::open(&path).unwrap();
2303 let volume = crate::q42_volume::Q42RangeVolume::open(source).unwrap();
2304 let mut context = SparqlQueryContext::new();
2305 context.variable_count = 1;
2306 let plan = Q42RangeNestedLoopJoinPlan {
2307 left: Q42RangeTriplePattern {
2308 subject: left.subject,
2309 predicate: left.predicate,
2310 object: 0,
2311 },
2312 right: Q42RangeTriplePattern {
2313 subject: 0,
2314 predicate: right.predicate,
2315 object: right.object,
2316 },
2317 };
2318 let mut compressed = [0u8; crate::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
2319 let mut decoded = [0u8; crate::q42_volume::SUPERBLOCK_SIZE];
2320 let mut quins = [NQuin::default(); 1];
2321 let mut left_rows = [BindingRow::default(); 1];
2322 let mut right_rows = [BindingRow::default(); 1];
2323 let mut out = [BindingRow::default(); 1];
2324 let mut state = Q42RangeNestedLoopJoinState::default();
2325
2326 let first = execute_range_nested_loop_join_page_into(
2327 &volume,
2328 plan,
2329 &context,
2330 &mut state,
2331 &mut compressed,
2332 &mut decoded,
2333 &mut quins,
2334 &mut left_rows,
2335 &mut right_rows,
2336 &mut out,
2337 )
2338 .unwrap();
2339 assert_eq!(first.returned, 1);
2340 assert_eq!(out[0].slots[0], Some(right.subject));
2341 assert!(!first.done);
2342
2343 let final_page = execute_range_nested_loop_join_page_into(
2344 &volume,
2345 plan,
2346 &context,
2347 &mut state,
2348 &mut compressed,
2349 &mut decoded,
2350 &mut quins,
2351 &mut left_rows,
2352 &mut right_rows,
2353 &mut out,
2354 )
2355 .unwrap();
2356 assert_eq!(final_page.returned, 0);
2357 assert!(final_page.done);
2358 }
2359
2360 #[cfg(not(target_arch = "wasm32"))]
2361 #[test]
2362 fn range_volume_set_nested_loop_join_scans_manifest_children() {
2363 let dir = tempfile::TempDir::new().unwrap();
2364 let child_path = dir.path().join("child.q42");
2365 let root_path = dir.path().join("root.q42");
2366 let left = NQuin {
2367 subject: 10,
2368 predicate: 20,
2369 object: 30,
2370 context: 0,
2371 metadata: 0,
2372 parity: 0,
2373 };
2374 let right = NQuin {
2375 subject: 30,
2376 predicate: 40,
2377 object: 50,
2378 context: 0,
2379 metadata: 0,
2380 parity: 0,
2381 };
2382 crate::q42_volume::write_unified_volume(
2383 &child_path,
2384 &std::collections::HashMap::new(),
2385 &[(left.object, right.object)],
2386 &[vec![left, right]],
2387 )
2388 .unwrap();
2389 let manifest = crate::q42_volume::Q42VolumeManifest {
2390 generation: 1,
2391 segments: vec![crate::q42_volume::Q42VolumeManifest::segment_from_file(
2392 &child_path,
2393 "child.q42".to_string(),
2394 )
2395 .unwrap()],
2396 lexicon_segments: Vec::new(),
2397 };
2398 crate::q42_volume::write_volume_root(&root_path, &manifest).unwrap();
2399 let root_source = crate::q42_volume::LocalFileRangeSource::open(&root_path).unwrap();
2400 let root = crate::q42_volume::Q42RangeVolume::open(root_source).unwrap();
2401 let factory = |entry: &crate::q42_volume::Q42VolumeSegment| {
2402 crate::q42_volume::LocalFileRangeSource::open(&dir.path().join(&entry.locator))
2403 };
2404 let volumes = crate::q42_volume::Q42RangeVolumeSet::open_root(&root, &factory).unwrap();
2405 let mut context = SparqlQueryContext::new();
2406 context.variable_count = 1;
2407 let plan = Q42RangeNestedLoopJoinPlan {
2408 left: Q42RangeTriplePattern {
2409 subject: left.subject,
2410 predicate: left.predicate,
2411 object: 0,
2412 },
2413 right: Q42RangeTriplePattern {
2414 subject: 0,
2415 predicate: right.predicate,
2416 object: right.object,
2417 },
2418 };
2419 let mut compressed = [0u8; crate::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
2420 let mut decoded = [0u8; crate::q42_volume::SUPERBLOCK_SIZE];
2421 let mut quins = [NQuin::default(); 1];
2422 let mut left_rows = [BindingRow::default(); 1];
2423 let mut right_rows = [BindingRow::default(); 1];
2424 let mut out = [BindingRow::default(); 1];
2425 let mut state = Q42RangeVolumeSetNestedLoopJoinState::default();
2426 let page = execute_range_volume_set_nested_loop_join_page_into(
2427 &volumes,
2428 plan,
2429 &context,
2430 &mut state,
2431 &mut compressed,
2432 &mut decoded,
2433 &mut quins,
2434 &mut left_rows,
2435 &mut right_rows,
2436 &mut out,
2437 )
2438 .unwrap();
2439 assert_eq!(page.returned, 1);
2440 assert_eq!(out[0].slots[0], Some(right.subject));
2441 }
2442}