1pub type PatternId = u16;
7pub type ExpressionId = u16;
8pub type VariableId = u8;
9
10pub const MAX_PATTERNS: usize = 128;
12
13pub const MAX_EXPRESSIONS: usize = 128;
15
16pub const MAX_VARIABLES: usize = 16;
18
19pub const MAX_BINDINGS: usize = 16;
21
22pub const MAX_ORDER_CONDITIONS: usize = 16;
24
25#[repr(C)]
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum SparqlQuery {
29 Select(SelectQuery),
30 Ask(AskQuery),
31 Construct(ConstructQuery),
32 Describe(DescribeQuery),
33}
34
35#[repr(C)]
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct SelectQuery {
39 pub distinct: bool,
40 pub reduced: bool,
41 pub variables: [VariableId; MAX_VARIABLES],
42 pub var_count: u8,
43 pub root_pattern: PatternId,
44 pub group_by: [VariableId; MAX_VARIABLES],
45 pub group_by_count: u8,
46 pub aggregates: [crate::sparql_planner::AggregateSpec; 16],
47 pub aggregate_count: u8,
48 pub having: Option<ExpressionId>,
49 pub order_by: [OrderCondition; MAX_ORDER_CONDITIONS],
50 pub order_by_count: u8,
51 pub limit: Option<u64>,
52 pub offset: u64,
53}
54
55impl Default for SelectQuery {
56 fn default() -> Self {
57 Self {
58 distinct: false,
59 reduced: false,
60 variables: [0; MAX_VARIABLES],
61 var_count: 0,
62 root_pattern: 0,
63 group_by: [0; MAX_VARIABLES],
64 group_by_count: 0,
65 aggregates: [crate::sparql_planner::AggregateSpec {
66 func: 0,
67 input_var: 0,
68 output_var: 0,
69 }; 16],
70 aggregate_count: 0,
71 having: None,
72 order_by: [OrderCondition::default(); MAX_ORDER_CONDITIONS],
73 order_by_count: 0,
74 limit: None,
75 offset: 0,
76 }
77 }
78}
79
80#[repr(C)]
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub struct AskQuery {
84 pub root_pattern: PatternId,
85}
86
87#[repr(C)]
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct ConstructQuery {
91 pub template_pattern: PatternId,
92 pub root_pattern: PatternId,
93 pub group_by: [VariableId; MAX_VARIABLES],
94 pub group_by_count: u8,
95 pub having: Option<ExpressionId>,
96 pub order_by: [OrderCondition; MAX_ORDER_CONDITIONS],
97 pub order_by_count: u8,
98 pub limit: Option<u64>,
99 pub offset: u64,
100}
101
102#[repr(C)]
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct DescribeQuery {
106 pub vars_or_ids: [u64; MAX_VARIABLES],
107 pub var_count: u8,
108 pub root_pattern: Option<PatternId>,
109}
110
111#[repr(u8)]
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum TemporalMode {
117 AsOf = 0,
119 AtTime = 1,
121}
122
123#[repr(C)]
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum Pattern {
127 Triple {
129 subject: u64,
130 predicate: u64,
131 object: u64,
132 },
133 Optional { inner: PatternId },
135 Union { left: PatternId, right: PatternId },
137 Graph {
139 graph_var_or_id: u64,
140 inner: PatternId,
141 },
142 Filter {
144 pattern: PatternId,
145 expression: ExpressionId,
146 },
147 Bind {
151 pattern: PatternId,
152 var: VariableId,
153 expression: ExpressionId,
154 },
155 Minus { inner: PatternId },
157 Group { start_idx: u16, len: u16 },
159 PropertyPath {
161 subject: u64,
162 path: PathId,
163 object: u64,
164 },
165 Service {
167 endpoint_did_id: u64, inner_pattern: PatternId,
169 },
170 AsOf {
175 inner: PatternId,
176 timestamp_ms: u64,
177 mode: TemporalMode,
178 },
179 StarTriple {
181 inner_subject: u64,
182 inner_predicate: u64,
183 inner_object: u64,
184 outer_predicate: u64,
185 outer_object: u64,
186 },
187 SubSelect { query_id: u16 },
192}
193
194#[repr(C)]
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum Path {
198 Predicate(u64),
200 Inverse(PathId),
202 Sequence { left: PathId, right: PathId },
204 Alternative { left: PathId, right: PathId },
206 ZeroOrMore(PathId),
208 OneOrMore(PathId),
210 ZeroOrOne(PathId),
212}
213
214pub type PathId = u16;
215
216#[repr(C)]
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum Expression {
220 Variable(VariableId),
222 Literal(u64),
224 Iri(u64),
226 UnaryOp { op: UnaryOp, expr: ExpressionId },
228 BinaryOp {
230 op: BinaryOp,
231 left: ExpressionId,
232 right: ExpressionId,
233 },
234 Function {
236 func: Function,
237 args_start: u16,
238 args_len: u16,
239 },
240 Subquery {
242 query_id: u16, },
244 EmbeddedTriple {
246 subject: u64,
247 predicate: u64,
248 object: u64,
249 },
250 Exists { pattern: PatternId, negated: bool },
256}
257
258#[repr(C)]
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261pub enum UnaryOp {
262 Not,
263 Plus,
264 Minus,
265}
266
267#[repr(C)]
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub enum BinaryOp {
271 Or,
272 And,
273 Equal,
274 NotEqual,
275 LessThan,
276 LessThanOrEqual,
277 GreaterThan,
278 GreaterThanOrEqual,
279 Add,
280 Subtract,
281 Multiply,
282 Divide,
283}
284
285#[repr(C)]
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum Function {
289 Str,
290 Lang,
291 LangMatches,
292 Datatype,
293 Bound,
294 Iri,
295 Uri,
296 Bnode,
297 Rand,
298 Abs,
299 Ceil,
300 Floor,
301 Round,
302 Concat,
303 Substring,
304 Strlen,
305 Ucase,
306 Lcase,
307 EncodeForUri,
308 Contains,
309 VarStarts,
310 VarEnds,
311 StrBefore,
312 StrAfter,
313 Year,
314 Month,
315 Day,
316 Hours,
317 Minutes,
318 Seconds,
319 Timezone,
320 Tz,
321 Now,
322 Uuid,
323 StrUuid,
324 Coalesce,
325 If,
326 StrLang,
327 StrDt,
328 SameTerm,
329 IsIri,
330 IsUri,
331 IsBlank,
332 IsLiteral,
333 IsNumeric,
334 Regex,
335 TripleSubject,
337 TriplePredicate,
338 TripleObject,
339 Triple,
340 Custom(u64), }
342
343#[repr(C)]
345#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
346pub struct OrderCondition {
347 pub ascending: bool,
348 pub expr: ExpressionId,
349}
350
351#[repr(C)]
353pub struct SparqlQueryContext {
354 pub patterns: [Pattern; MAX_PATTERNS],
356 pub pattern_count: usize,
358 pub expressions: [Expression; MAX_EXPRESSIONS],
360 pub expression_count: usize,
362 pub paths: [Path; MAX_PATTERNS],
364 pub path_count: usize,
366 pub subqueries: [SparqlQuery; 16],
368 pub subquery_count: usize,
370 pub variable_hashes: [u64; MAX_VARIABLES],
372 pub variable_count: usize,
374 pub function_args: [ExpressionId; 64],
376 pub function_arg_count: usize,
378}
379
380pub fn literal_term_hash(text: &str, lang: Option<&str>, datatype: Option<&str>) -> u64 {
390 match (lang, datatype) {
391 (None, None) => crate::lexicon::generate_60bit_token(text.as_bytes()),
392 (Some(l), _) => crate::lexicon::generate_60bit_token(format!("{text}@{l}").as_bytes()),
393 (None, Some(d)) => crate::lexicon::generate_60bit_token(format!("{text}^^{d}").as_bytes()),
394 }
395}
396
397#[derive(Debug, Default, Clone)]
403pub struct LiteralTable {
404 entries: Vec<(u64, String, Option<String>, Option<String>)>,
405}
406
407impl LiteralTable {
408 pub fn new() -> Self {
409 Self::default()
410 }
411 pub fn intern(&mut self, hash: u64, text: &str) {
413 self.intern_tagged(hash, text, None, None);
414 }
415 pub fn intern_tagged(
417 &mut self,
418 hash: u64,
419 text: &str,
420 lang: Option<&str>,
421 datatype: Option<&str>,
422 ) {
423 if self.entries.iter().any(|(h, ..)| *h == hash) {
424 return;
425 }
426 self.entries.push((
427 hash,
428 text.to_string(),
429 lang.map(str::to_string),
430 datatype.map(str::to_string),
431 ));
432 }
433 pub fn resolve(&self, hash: u64) -> Option<&str> {
434 self.entries
435 .iter()
436 .find(|(h, ..)| *h == hash)
437 .map(|(_, s, ..)| s.as_str())
438 }
439 pub fn lang(&self, hash: u64) -> Option<&str> {
441 self.entries
442 .iter()
443 .find(|(h, ..)| *h == hash)
444 .and_then(|(_, _, l, _)| l.as_deref())
445 }
446 pub fn datatype(&self, hash: u64) -> Option<&str> {
448 self.entries
449 .iter()
450 .find(|(h, ..)| *h == hash)
451 .and_then(|(_, _, _, d)| d.as_deref())
452 }
453 pub fn contains(&self, hash: u64) -> bool {
455 self.entries.iter().any(|(h, ..)| *h == hash)
456 }
457 pub fn len(&self) -> usize {
458 self.entries.len()
459 }
460 pub fn is_empty(&self) -> bool {
461 self.entries.is_empty()
462 }
463}
464
465#[derive(Debug, Default)]
477pub struct StringSink {
478 produced: std::cell::RefCell<Vec<(u64, String, Option<String>, Option<String>)>>,
479}
480
481impl StringSink {
482 pub fn new() -> Self {
483 Self::default()
484 }
485 pub fn intern(&self, text: &str) -> u64 {
489 self.intern_tagged(text, None, None)
490 }
491 pub fn intern_tagged(&self, text: &str, lang: Option<&str>, datatype: Option<&str>) -> u64 {
495 let hash = literal_term_hash(text, lang, datatype);
496 let mut v = self.produced.borrow_mut();
497 if !v.iter().any(|(h, ..)| *h == hash) {
498 v.push((
499 hash,
500 text.to_string(),
501 lang.map(str::to_string),
502 datatype.map(str::to_string),
503 ));
504 }
505 hash
506 }
507 pub fn resolve(&self, hash: u64) -> Option<String> {
509 self.produced
510 .borrow()
511 .iter()
512 .find(|(h, ..)| *h == hash)
513 .map(|(_, s, ..)| s.clone())
514 }
515 pub fn lang(&self, hash: u64) -> Option<String> {
517 self.produced
518 .borrow()
519 .iter()
520 .find(|(h, ..)| *h == hash)
521 .and_then(|(_, _, l, _)| l.clone())
522 }
523 pub fn datatype(&self, hash: u64) -> Option<String> {
525 self.produced
526 .borrow()
527 .iter()
528 .find(|(h, ..)| *h == hash)
529 .and_then(|(_, _, _, d)| d.clone())
530 }
531}
532
533#[derive(Clone, Copy)]
542pub struct TextResolver<'a> {
543 pub literals: &'a LiteralTable,
544 pub lexicon: Option<&'a dyn Fn(u64) -> Option<String>>,
545 pub sink: Option<&'a StringSink>,
547 pub now_ms: u64,
550 pub seed: u64,
552}
553
554impl<'a> TextResolver<'a> {
555 pub fn new(literals: &'a LiteralTable) -> Self {
556 Self {
557 literals,
558 lexicon: None,
559 sink: None,
560 now_ms: 0,
561 seed: 0,
562 }
563 }
564 pub fn with_lexicon(
565 literals: &'a LiteralTable,
566 lexicon: &'a dyn Fn(u64) -> Option<String>,
567 ) -> Self {
568 Self {
569 literals,
570 lexicon: Some(lexicon),
571 sink: None,
572 now_ms: 0,
573 seed: 0,
574 }
575 }
576 pub fn with_sink(mut self, sink: &'a StringSink) -> Self {
578 self.sink = Some(sink);
579 self
580 }
581 pub fn with_env(mut self, now_ms: u64, seed: u64) -> Self {
584 self.now_ms = now_ms;
585 self.seed = seed;
586 self
587 }
588 pub fn resolve_text(&self, hash: u64) -> Option<String> {
590 if let Some(s) = self.literals.resolve(hash) {
591 return Some(s.to_string());
592 }
593 if let Some(sink) = self.sink {
594 if let Some(s) = sink.resolve(hash) {
595 return Some(s);
596 }
597 }
598 if let Some(f) = self.lexicon {
599 if let Some(s) = f(hash) {
600 return Some(s);
601 }
602 }
603 crate::resolver::resolve_hash(hash).and_then(|b| String::from_utf8(b.to_vec()).ok())
604 }
605
606 pub fn lang_of(&self, hash: u64) -> Option<String> {
610 if let Some(l) = self.literals.lang(hash) {
611 return Some(l.to_string());
612 }
613 if let Some(sink) = self.sink {
614 if let Some(l) = sink.lang(hash) {
615 return Some(l);
616 }
617 }
618 if self.literals.contains(hash)
620 || self
621 .sink
622 .map(|s| s.resolve(hash).is_some())
623 .unwrap_or(false)
624 || crate::resolver::classify_inline_literal(hash).is_some()
625 {
626 return Some(String::new());
627 }
628 None
629 }
630
631 pub fn datatype_of(&self, hash: u64) -> Option<String> {
636 const XSD_STRING: &str = "http://www.w3.org/2001/XMLSchema#string";
637 const RDF_LANGSTRING: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString";
638 if let Some(dt) = self.literals.datatype(hash) {
639 return Some(dt.to_string());
640 }
641 if let Some(sink) = self.sink {
642 if let Some(dt) = sink.datatype(hash) {
643 return Some(dt);
644 }
645 if sink.lang(hash).is_some() {
646 return Some(RDF_LANGSTRING.to_string());
647 }
648 }
649 if self.literals.lang(hash).is_some() {
650 return Some(RDF_LANGSTRING.to_string());
651 }
652 if let Some(lit) = crate::resolver::classify_inline_literal(hash) {
653 return Some(lit.datatype_iri().to_string());
654 }
655 if self.literals.contains(hash)
656 || self
657 .sink
658 .map(|s| s.resolve(hash).is_some())
659 .unwrap_or(false)
660 {
661 return Some(XSD_STRING.to_string());
662 }
663 None
664 }
665}
666
667impl SparqlQueryContext {
668 pub fn new() -> Self {
669 Self {
670 patterns: [Pattern::Triple {
671 subject: 0,
672 predicate: 0,
673 object: 0,
674 }; MAX_PATTERNS],
675 pattern_count: 0,
676 expressions: [Expression::Variable(0); MAX_EXPRESSIONS],
677 expression_count: 0,
678 paths: [Path::Predicate(0); MAX_PATTERNS],
679 path_count: 0,
680 subqueries: [SparqlQuery::Select(SelectQuery {
681 distinct: false,
682 reduced: false,
683 variables: [0; MAX_VARIABLES],
684 var_count: 0,
685 root_pattern: 0,
686 group_by: [0; MAX_VARIABLES],
687 group_by_count: 0,
688 aggregates: [crate::sparql_planner::AggregateSpec {
689 func: 0,
690 input_var: 0,
691 output_var: 0,
692 }; 16],
693 aggregate_count: 0,
694 having: None,
695 order_by: [OrderCondition {
696 expr: 0,
697 ascending: true,
698 }; MAX_ORDER_CONDITIONS],
699 order_by_count: 0,
700 limit: None,
701 offset: 0,
702 }); 16],
703 subquery_count: 0,
704 variable_hashes: [0; MAX_VARIABLES],
705 variable_count: 0,
706 function_args: [0; 64],
707 function_arg_count: 0,
708 }
709 }
710
711 pub fn alloc_pattern(&mut self, pattern: Pattern) -> Result<PatternId, String> {
713 if self.pattern_count >= MAX_PATTERNS {
714 return Err("Pattern overflow".to_string());
715 }
716 let id = self.pattern_count as PatternId;
717 self.patterns[self.pattern_count] = pattern;
718 self.pattern_count += 1;
719 Ok(id)
720 }
721
722 pub fn alloc_expression(&mut self, expr: Expression) -> Result<ExpressionId, String> {
724 if self.expression_count >= MAX_EXPRESSIONS {
725 return Err("Expression overflow".to_string());
726 }
727 let id = self.expression_count as ExpressionId;
728 self.expressions[self.expression_count] = expr;
729 self.expression_count += 1;
730 Ok(id)
731 }
732
733 pub fn alloc_path(&mut self, path: Path) -> Result<PathId, String> {
735 if self.path_count >= MAX_PATTERNS {
736 return Err("Path overflow".to_string());
737 }
738 let id = self.path_count as PathId;
739 self.paths[self.path_count] = path;
740 self.path_count += 1;
741 Ok(id)
742 }
743
744 pub fn alloc_subquery(&mut self, query: SparqlQuery) -> Result<u16, String> {
746 if self.subquery_count >= 16 {
747 return Err("Subquery overflow".to_string());
748 }
749 let id = self.subquery_count as u16;
750 self.subqueries[self.subquery_count] = query;
751 self.subquery_count += 1;
752 Ok(id)
753 }
754
755 pub fn register_variable(&mut self, name: &str) -> Result<VariableId, String> {
757 if self.variable_count >= MAX_VARIABLES {
758 return Err("Variable overflow".to_string());
759 }
760 let hash = crate::lexicon::generate_60bit_token(name.as_bytes());
761 for (i, var_hash) in self.variable_hashes.iter().enumerate() {
763 if *var_hash == hash {
764 return Ok(i as VariableId);
765 }
766 }
767 let id = self.variable_count as VariableId;
768 self.variable_hashes[self.variable_count] = hash;
769 self.variable_count += 1;
770 Ok(id)
771 }
772
773 pub fn reset(&mut self) {
775 self.pattern_count = 0;
776 self.expression_count = 0;
777 self.variable_count = 0;
778 self.function_arg_count = 0;
779 self.variable_hashes = [0; MAX_VARIABLES];
780 }
781}
782
783impl Default for SparqlQueryContext {
784 fn default() -> Self {
785 Self::new()
786 }
787}
788
789#[repr(C)]
791#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
792pub struct BindingRow {
793 pub slots: [Option<u64>; MAX_BINDINGS],
795}
796
797impl BindingRow {
798 pub fn new() -> Self {
799 Self {
800 slots: [None; MAX_BINDINGS],
801 }
802 }
803
804 pub fn set(&mut self, var_id: VariableId, value: u64) {
805 if (var_id as usize) < MAX_BINDINGS {
806 self.slots[var_id as usize] = Some(value);
807 }
808 }
809
810 pub fn get(&self, var_id: VariableId) -> Option<u64> {
811 if (var_id as usize) < MAX_BINDINGS {
812 self.slots[var_id as usize]
813 } else {
814 None
815 }
816 }
817
818 pub fn clear(&mut self) {
819 self.slots = [None; MAX_BINDINGS];
820 }
821}
822
823impl Default for BindingRow {
824 fn default() -> Self {
825 Self::new()
826 }
827}
828
829pub trait PhysicalOperator {
831 fn next(&mut self, ctx: &SparqlQueryContext, row: &mut BindingRow) -> bool;
833}
834
835#[cfg(test)]
836mod tests {
837 use super::*;
838
839 #[test]
840 fn test_query_context_allocation() {
841 let mut ctx = SparqlQueryContext::new();
842
843 let pattern = Pattern::Triple {
844 subject: 1,
845 predicate: 2,
846 object: 3,
847 };
848
849 let id = ctx.alloc_pattern(pattern).unwrap();
850 assert_eq!(id, 0);
851 assert_eq!(ctx.pattern_count, 1);
852 }
853
854 #[test]
855 fn test_variable_registration() {
856 let mut ctx = SparqlQueryContext::new();
857
858 let id1 = ctx.register_variable("?x").unwrap();
859 let id2 = ctx.register_variable("?y").unwrap();
860
861 assert_eq!(id1, 0);
862 assert_eq!(id2, 1);
863 assert_eq!(ctx.variable_count, 2);
864 }
865
866 #[test]
867 fn test_variable_duplicate() {
868 let mut ctx = SparqlQueryContext::new();
869
870 let id1 = ctx.register_variable("?x").unwrap();
871 let id2 = ctx.register_variable("?x").unwrap();
872
873 assert_eq!(id1, id2);
874 assert_eq!(ctx.variable_count, 1);
875 }
876
877 #[test]
878 fn test_binding_row() {
879 let mut row = BindingRow::new();
880
881 row.set(0, 42);
882 assert_eq!(row.get(0), Some(42));
883 assert_eq!(row.get(1), None);
884 }
885
886 #[test]
887 fn test_optional_pattern_index() {
888 let mut ctx = SparqlQueryContext::new();
889
890 let inner = Pattern::Triple {
891 subject: 1,
892 predicate: 2,
893 object: 3,
894 };
895 let inner_id = ctx.alloc_pattern(inner).unwrap();
896
897 let optional = Pattern::Optional { inner: inner_id };
898 let optional_id = ctx.alloc_pattern(optional).unwrap();
899
900 assert_eq!(ctx.pattern_count, 2);
901 if let Pattern::Optional { inner } = ctx.patterns[optional_id as usize] {
902 assert_eq!(inner, inner_id);
903 } else {
904 panic!("Expected Optional pattern");
905 }
906 }
907
908 #[test]
909 fn test_union_pattern_index() {
910 let mut ctx = SparqlQueryContext::new();
911
912 let left = Pattern::Triple {
913 subject: 1,
914 predicate: 2,
915 object: 3,
916 };
917 let right = Pattern::Triple {
918 subject: 4,
919 predicate: 5,
920 object: 6,
921 };
922
923 let left_id = ctx.alloc_pattern(left).unwrap();
924 let right_id = ctx.alloc_pattern(right).unwrap();
925
926 let union = Pattern::Union {
927 left: left_id,
928 right: right_id,
929 };
930 let union_id = ctx.alloc_pattern(union).unwrap();
931
932 assert_eq!(ctx.pattern_count, 3);
933 if let Pattern::Union { left, right } = ctx.patterns[union_id as usize] {
934 assert_eq!(left, left_id);
935 assert_eq!(right, right_id);
936 } else {
937 panic!("Expected Union pattern");
938 }
939 }
940}