Skip to main content

qualia_core_db/sparql_library/
sparql_executor.rs

1//! SPARQL Physical Query Executor
2//!
3//! Executes query plans against NQuin arrays using zero-allocation patterns.
4
5use 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/// Resume state for a caller-buffered triple-pattern page over a range-backed
18/// Q42 segment.  It is deliberately separate from the resident executor: no
19/// graph-sized `Vec<NQuin>` is constructed on this path.
20#[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/// A range-executable subset of a physical plan: one triple pattern with
43/// optional projection and LIMIT/OFFSET. Unsupported trees are rejected so a
44/// caller can deliberately choose the resident compatibility executor instead
45/// of receiving a partial SPARQL result.
46#[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/// Resumable state for the logical-volume equivalent of
115/// [`Q42RangeNestedLoopJoinState`].  The cursors include manifest child
116/// positions, so a join never has to re-open or materialise a volume set.
117#[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
165/// Execute a bounded page of a two-pattern nested-loop join. `left_rows` and
166/// `right_rows` remain owned by the caller across invocations; the state holds
167/// only fixed-size cursors and counts. The output buffer must be at least as
168/// large as the Quin scratch buffer so no right-page result is dropped.
169pub 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
276/// Execute one bounded page of a two-pattern nested-loop join across a
277/// manifest-backed Q42 snapshot.  It has the same binding and output contract
278/// as [`execute_range_nested_loop_join_page_into`], while the child selection
279/// remains inside [`Q42RangeVolumeSet`].
280pub 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
430/// Execute one bounded page of a simple SELECT/ASK physical plan.
431pub 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
512/// Execute one bounded page of the same simple SELECT/ASK plan across a
513/// front-manifested logical Q42 root.
514pub 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
595/// Execute one physical page of a SPARQL triple pattern against a Q42 range
596/// source.  Constants and already-bound variables become on-disk filters; a
597/// bound object selects BIDX pruning automatically. `quin_scratch` and `out`
598/// are caller-owned, maintaining the zero-heap query kernel contract.
599pub 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
656/// The logical-volume counterpart of [`execute_range_triple_page_into`]. It
657/// preserves exactly the same SPARQL binding semantics while the root manifest
658/// prunes child segments before each range-backed SuperBlock scan.
659pub 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} // range_q42_exec — native mmap/HTTP range SPARQL; not on wasm32.
720
721#[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
749/// Query executor
750pub struct QueryExecutor<'a> {
751    pub quins: &'a [NQuin],
752    /// Optional text resolver for literal-text functions (`geof:*`, …). `None`
753    /// on the plain slice path; supplied when a lexicon / query-literal table is
754    /// available so extension functions can recover geometry/string text.
755    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    /// Executor with a text resolver, enabling `geof:*`/text extension functions.
767    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    /// Execute a query plan and return results
778    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    /// Execute ASK query
797    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    /// Collect the concrete triple patterns of a CONSTRUCT template into `out`
811    /// as `(subject, predicate, object)` term triples (each field still a
812    /// variable id or a constant hash). A template is either a single `Triple`
813    /// or a `Group` of triples; any non-triple child is ignored (a CONSTRUCT
814    /// template is a basic graph pattern, so only triples are meaningful).
815    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    /// Execute a CONSTRUCT query: evaluate the WHERE pattern, then instantiate
836    /// the template for every solution. The returned rows are the constructed
837    /// triples themselves — variable slot 0 = subject, 1 = predicate, 2 = object
838    /// — which is exactly what the N-Triples/XML/JSON graph serialisers read.
839    ///
840    /// A template triple is emitted only when all three of its terms are bound
841    /// (SPARQL 1.1 §16.2.1: a template instantiation with an unbound term
842    /// produces no triple). Duplicate triples are collapsed.
843    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), // unbound → None → triple skipped
857                None => Some(term),        // constant term
858            }
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    /// Execute a DESCRIBE query: build the set of resources to describe (each
882    /// `vars_or_ids` entry is a constant IRI, or a variable bound by the WHERE
883    /// pattern; `DESCRIBE *` with a WHERE describes every value bound in the
884    /// solutions), then emit a Concise Bounded Description — every stored quin
885    /// whose subject is a described resource. Rows carry the triple in slots
886    /// 0/1/2, matching the graph serialisers. Duplicate triples are collapsed.
887    pub fn execute_describe(
888        &self,
889        plan: &ExecutionPlan,
890        ctx: &SparqlQueryContext,
891        describe: &DescribeQuery,
892    ) -> Result<Vec<BindingRow>, String> {
893        // WHERE solutions are only needed (and only valid) when a pattern exists
894        // — a bare `DESCRIBE <iri>` has an empty plan.
895        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            // DESCRIBE * — every value bound in the WHERE solutions.
910            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        // Scan all quins matching the subject
1100        for quin in self.quins {
1101            if quin.subject == subject {
1102                // Bind the subject if it's a variable
1103                // For now, just add the quin to results
1104                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        // Zero-allocation Merge Join (O(N log N) + O(M log M))
1269        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 join_var is None on either side, it conceptually matches anything.
1282            // However, in BGP joins, join variables are practically always bound.
1283            // If they are unbound, we fall back to nested loop for those specific rows (not implemented here,
1284            // assume BGP variables are bound).
1285            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        // Full Cross-Product with Compatibility Check
1353        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        // Filter results based on expression evaluation. `eval_filter_bool`
1391        // handles FILTER (NOT) EXISTS in boolean position and delegates pure
1392        // value expressions to the expression evaluator.
1393        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    /// True iff the expression subtree contains an `EXISTS`/`NOT EXISTS` node
1403    /// (walking the boolean-combinator arms only — enough to route `&&`/`||`/`!`
1404    /// through `eval_filter_bool`; an EXISTS anywhere else falls through to the
1405    /// value evaluator, which rejects it honestly).
1406    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    /// Evaluate `EXISTS { pattern }` for the current row: plan the inner group,
1418    /// execute it seeded with the row's bindings (pre-bound variables act as the
1419    /// SPARQL substitution μ), and report whether ≥1 solution exists.
1420    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    /// Evaluate a FILTER/HAVING constraint to a boolean, resolving `EXISTS`/`NOT
1436    /// EXISTS` (which need graph access) at the top level and within `&&`/`||`/`!`
1437    /// combinators, and delegating everything else to the value evaluator.
1438    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            // SPARQL 1.1 Extend: bind `var` to the value of the expression,
1501            // keeping every row. If the expression raises an error (e.g. a
1502            // not-yet-implemented string-producing builtin, or a type error),
1503            // the variable is simply left UNBOUND rather than failing the whole
1504            // query. Value-producing results (numeric / term / boolean /
1505            // already-interned string) map to the row's u64 slot.
1506            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                    // BIND of a real value: store the IEEE-754 bit pattern in the u64 slot.
1520                    input_row.set(var, f.to_bits());
1521                }
1522                Err(_) => { /* expression error → leave `var` unbound */ }
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        // Project each solution onto the selected variables only, dropping every
1541        // other binding. This is required for correctness — a downstream DISTINCT
1542        // must dedup on the *projected* columns, not on the full WHERE row (two
1543        // solutions identical in ?a but differing in an unselected ?b are one
1544        // DISTINCT ?a result). Variables keep their own slot ids (the serialiser
1545        // reads by variable id from the SELECT list).
1546        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        // Apply offset and limit
1574        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        // Sort in-place using the order_by expressions
1603        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        // Execute left
1640        self.execute_operator(left, plan, ctx, row, results)?;
1641
1642        // Execute right
1643        self.execute_operator(right, plan, ctx, row, results)?;
1644
1645        // SPARQL UNION is a multiset union (bag union), so no deduplication is needed.
1646
1647        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        // Execute left pattern
1660        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 each left result, try to execute right pattern
1665        for left_result in left_results {
1666            right_results.clear();
1667            let mut right_row = left_result; // Copy left bindings
1668            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                // Right pattern matched - combine bindings
1673                results.extend_from_slice(&right_results);
1674            } else {
1675                // Right pattern didn't match - keep left result with NULL for right variables
1676                results.push(left_result);
1677            }
1678        }
1679
1680        Ok(!results.is_empty())
1681    }
1682
1683    /// SPARQL 1.1 MINUS (anti-join). Both sides are evaluated; a left solution
1684    /// is removed iff some right solution is **compatible** with it AND the two
1685    /// **share at least one bound variable** (the domain-intersection rule that
1686    /// distinguishes MINUS from NOT EXISTS — MINUS over disjoint domains removes
1687    /// nothing).
1688    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        // The right side is evaluated independently (fresh bindings).
1701        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    /// Execute a sub-`SELECT`: evaluate the stored subquery independently, then
1729    /// join each of its projected solutions with the current bindings on shared
1730    /// variables (a solution incompatible on any shared, differently-bound
1731    /// variable is dropped). Only the sub-select's projected variables are
1732    /// visible here — its internal variables were removed by its own projection.
1733    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        // Group results by group variables
1812        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        // Convert groups to binding rows
1828        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            // Write aggregate results to output variables
1836            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        // Filter results based on HAVING expression (EXISTS-aware, like FILTER).
1861        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                // Simple predicate - same as triple scan
1887                self.execute_triple_scan(subject, *pred, object, ctx, row, results)
1888            }
1889            crate::sparql_ast::Path::Inverse(inner_id) => {
1890                // Inverse - swap subject and object
1891                self.execute_property_path(object, *inner_id, subject, ctx, row, results)
1892            }
1893            crate::sparql_ast::Path::Sequence { left, right } => {
1894                // Sequence - execute left then right
1895                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                // Alternation - execute left OR right
1913                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                // Kleene star `*`: the FULL reflexive-transitive closure of the inner
1925                // path from `subject`, computed as a cycle-safe fixpoint (not a fixed
1926                // hop limit). `subject` itself matches the zero-length path.
1927                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                // `+`: the FULL (non-reflexive) transitive closure of the inner path from
1934                // `subject`, cycle-safe. `subject` is included only if a cycle reaches it
1935                // back via ≥1 hop.
1936                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                // Zero or one - either direct or via path
1942                // Direct match
1943                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                // Via path
1950                self.execute_property_path(subject, *inner_id, object, ctx, row, results)
1951            }
1952        }
1953    }
1954
1955    /// Nodes reachable from `subject` via **one or more** applications of the inner
1956    /// property path — the full transitive closure, made cycle-safe by expanding each
1957    /// node at most once. Replaces the former fixed "up to 3 hops" truncation, which
1958    /// silently returned incomplete results for longer paths.
1959    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; // cycle guard: expand each node's out-edges at most once
1973            }
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    /// Emit a one-binding row (slot 0 = node) for each reachable node matching `object`
1986    /// (`object == 0` = unbound → emit all).
1987    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        // GRAPH ?g { … } — the graph term is a variable. Enumerate every named
2011        // graph (distinct non-default context), evaluate the inner pattern within
2012        // it, and bind ?g to that graph IRI on each resulting solution.
2013        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                // Seed the inner evaluation with ?g pre-bound so a join on ?g is
2032                // consistent; stamp it on every produced row as well.
2033                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        // GRAPH <iri> { … } — a specific named graph.
2048        let graph_id = graph_var_or_id;
2049
2050        // Filter quins by graph context
2051        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        // Create a temporary executor with graph-filtered quins, propagating the
2058        // text resolver so nested GRAPH/geo functions still resolve.
2059        let temp_executor = QueryExecutor {
2060            quins: &graph_quins,
2061            resolver: self.resolver,
2062        };
2063
2064        // Execute inner pattern with graph-filtered quins
2065        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        // Zero-allocation federated query execution
2078        // Use fixed-size network buffer instead of allocating per request
2079        let _network_buffer = [0u8; 4096];
2080
2081        // Check if DID has 0x8 prefix (identity recognition)
2082        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        // In production, this would:
2089        // 1. Resolve DID to get endpoint URL (using cached DID Document)
2090        // 2. Check connection pool for existing connection to endpoint
2091        // 3. Format SPARQL query request using zero-copy stack formatting
2092        // 4. Add DID-based authentication header (DID-LD/DID-JWT/DID-VC)
2093        // 5. Stream network response into network_buffer iteratively
2094        // 6. Parse response bytes directly to populate row slots
2095        // 7. Verify response signature using server DID
2096
2097        // Simplified: execute inner pattern locally for now
2098        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    /// Check whether `subject` satisfies the temporal constraint at `timestamp_ms`.
2129    ///
2130    /// Queries T_CONTEXT PROV-O quins for the subject.  Open-world assumption: if no
2131    /// temporal annotation is present, the quin is included.
2132    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
2180/// Unpack an embedded triple and map its components to variable indices in a BindingRow.
2181pub 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        // Should fail because root operator is invalid
2225        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}