1use spargebra::Query;
13
14use crate::bgp::{Binding, PatternTerm, TriplePattern};
15use crate::file::Rete;
16
17mod aggregate;
18mod eval;
19mod expr;
20mod lower;
21mod path;
22mod ql;
23
24use eval::{ask_solution, instantiate, raw_solutions, run_select, run_select_communities};
25use lower::{lower_pattern, lower_select, parse_query};
26pub use lower::{parse_select, query_predicates};
27pub(crate) use eval::eval_plan_in;
30
31#[derive(Debug, thiserror::Error)]
32#[non_exhaustive]
33pub enum SparqlError {
34 #[error("parse error: {0}")]
35 Parse(String),
36 #[error("unsupported query feature: {0}")]
37 Unsupported(&'static str),
38 #[error("SERVICE federation: {0}")]
42 Service(String),
43}
44
45#[derive(Debug, Clone)]
47pub struct Select {
48 pub project: Vec<String>,
50 pub distinct: bool,
52 pub offset: usize,
54 pub limit: Option<usize>,
56 pub group: Option<GroupSpec>,
58 pub extends: Vec<(String, FExpr)>,
61 pub order: Vec<(FExpr, bool)>,
63 pub having: Vec<FExpr>,
65 pub from: Vec<String>,
68 pub from_named: Option<Vec<String>>,
70 pub plan: Plan,
72 pub star_counter: usize,
77}
78
79impl Default for Select {
80 fn default() -> Self {
81 Select {
82 project: Vec::new(),
83 distinct: false,
84 offset: 0,
85 limit: None,
86 group: None,
87 extends: Vec::new(),
88 order: Vec::new(),
89 having: Vec::new(),
90 from: Vec::new(),
91 from_named: None,
92 plan: Plan::Bgp(Vec::new()),
93 star_counter: 0,
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct GroupSpec {
101 pub by: Vec<String>,
103 pub aggs: Vec<(String, Agg)>,
105 pub pre: Vec<(String, FExpr)>,
110}
111
112#[derive(Debug, Clone)]
114pub enum Agg {
115 CountStar {
117 distinct: bool,
118 },
119 Count(String, bool),
121 Sum(String),
122 Avg(String),
123 Min(String),
124 Max(String),
125 Sample(String),
127 GroupConcat(String, String, bool),
130}
131
132#[derive(Debug, Clone)]
134#[must_use]
135pub enum Plan {
136 Bgp(Vec<TriplePattern>),
138 Join(Box<Plan>, Box<Plan>),
140 Union(Box<Plan>, Box<Plan>),
142 LeftJoin(Box<Plan>, Box<Plan>, Option<FExpr>),
145 Filter(FExpr, Box<Plan>),
147 Extend(String, FExpr, Box<Plan>),
152 Path(PatternTerm, PathAst, PatternTerm),
154 Values(Vec<String>, Vec<Vec<Option<String>>>),
157 Minus(Box<Plan>, Box<Plan>),
160 Graph(GraphTarget, Box<Plan>),
162 Subquery(Box<Select>),
166 Service {
174 silent: bool,
175 endpoint: String,
177 vars: Vec<String>,
180 query: String,
182 },
183}
184
185#[derive(Debug, Clone)]
187pub enum GraphTarget {
188 Named(String),
190 Var(String),
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum Rep {
197 One,
199 OneOrMore,
201 ZeroOrMore,
203 ZeroOrOne,
205}
206
207#[derive(Debug, Clone)]
210pub enum PathAst {
211 Pred(String, bool),
213 Rep(Box<PathAst>, Rep),
215 Seq(Box<PathAst>, Box<PathAst>),
217 Alt(Box<PathAst>, Box<PathAst>),
219 NegatedSet(Vec<String>, bool),
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227pub enum Op {
228 Eq,
229 Ne,
230 Lt,
231 Le,
232 Gt,
233 Ge,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub enum ArithOp {
239 Add,
240 Sub,
241 Mul,
242 Div,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum Builtin {
249 Str,
250 StrLen,
251 UCase,
252 LCase,
253 Abs,
254 Ceil,
255 Floor,
256 Round,
257 Concat,
258 SubStr,
259 StrBefore,
260 StrAfter,
261 Contains,
262 StrStarts,
263 StrEnds,
264 IsIri,
265 IsBlank,
266 IsLiteral,
267 IsNumeric,
268 Datatype,
269 Lang,
270 Regex,
271 LangMatches,
272 StrDt,
273 StrLang,
274 Iri,
275 EncodeForUri,
276 Replace,
277 Md5,
278 Sha1,
279 Sha256,
280 Sha384,
281 Sha512,
282 Year,
283 Month,
284 Day,
285 Hours,
286 Minutes,
287 Seconds,
288 Timezone,
289 Tz,
290 CastInteger,
291 CastDecimal,
292 CastFloat,
293 CastDouble,
294 CastBoolean,
295 CastString,
296 Rand,
297 Uuid,
298 StrUuid,
299 BNode,
300 GeoSfContains,
303 GeoSfWithin,
304 GeoSfIntersects,
305 GeoSfDisjoint,
306 GeoSfEquals,
307 GeoDistance,
308 GeoEnvelope,
309 Geo3Distance,
311 Geo3Contains,
312 Geo3Within,
313 Geo3Adjacent,
314 TripleTerm,
317 IsTriple,
319 Subject,
321 Predicate,
322 Object,
323}
324
325#[derive(Debug, Clone)]
327pub enum FExpr {
328 Var(String),
329 Const(String),
331 Arith(ArithOp, Box<FExpr>, Box<FExpr>),
333 Func(Builtin, Vec<FExpr>),
335 Coalesce(Vec<FExpr>),
337 If(Box<FExpr>, Box<FExpr>, Box<FExpr>),
339 In(Box<FExpr>, Vec<FExpr>),
341 SameTerm(Box<FExpr>, Box<FExpr>),
343 Compare(Op, Box<FExpr>, Box<FExpr>),
344 And(Box<FExpr>, Box<FExpr>),
345 Or(Box<FExpr>, Box<FExpr>),
346 Not(Box<FExpr>),
347 Bound(String),
348 Exists(Box<Plan>),
351}
352
353type ExistsCache = std::collections::HashMap<*const Plan, ExistsEntry>;
356
357struct ExistsEntry {
361 sols: Vec<crate::row::Row>,
362 probe: Option<ExistsProbe>,
363}
364
365struct ExistsProbe {
369 svars: Vec<usize>,
371 jvars: Vec<usize>,
373 keys: std::collections::HashSet<Vec<crate::row::Val>>,
375 partial: Vec<crate::row::Row>,
377}
378
379fn build_exists_probe(b: &crate::row::Row, sols: &[crate::row::Row]) -> ExistsProbe {
382 let mask = crate::row::bound_mask(sols, b.len());
383 let svars: Vec<usize> = (0..b.len()).filter(|&i| mask[i]).collect();
384 let jvars: Vec<usize> = svars.iter().copied().filter(|&i| b[i].is_some()).collect();
385 let mut keys = std::collections::HashSet::new();
386 let mut partial = Vec::new();
387 for s in sols {
388 match jvars
389 .iter()
390 .map(|&i| s[i].clone())
391 .collect::<Option<Vec<crate::row::Val>>>()
392 {
393 Some(k) => {
394 keys.insert(k);
395 }
396 None => partial.push(s.clone()),
397 }
398 }
399 ExistsProbe {
400 svars,
401 jvars,
402 keys,
403 partial,
404 }
405}
406
407fn exists_matches(b: &crate::row::Row, entry: &ExistsEntry) -> bool {
411 let probe = entry.probe.as_ref().unwrap();
412 let bj: Vec<usize> = probe
413 .svars
414 .iter()
415 .copied()
416 .filter(|&i| b[i].is_some())
417 .collect();
418 if bj == probe.jvars {
419 let k: Vec<crate::row::Val> = probe.jvars.iter().map(|&i| b[i].clone().unwrap()).collect();
420 probe.keys.contains(&k)
421 || probe
422 .partial
423 .iter()
424 .any(|s| crate::row::compatible_rows(b, s))
425 } else {
426 entry.sols.iter().any(|s| crate::row::compatible_rows(b, s))
427 }
428}
429
430fn lexical(token: &str) -> String {
433 if let Some(rest) = token.strip_prefix('"') {
434 if let Some(end) = rest.find('"') {
435 return rest[..end].to_string();
436 }
437 }
438 token.to_string()
439}
440
441pub(crate) fn term_number(s: &str) -> Option<f64> {
445 as_number(s)
446}
447
448use crate::terms::as_number;
449
450fn compare(op: Op, a: &str, b: &str) -> bool {
452 use std::cmp::Ordering;
453 let ord = match (as_number(a), as_number(b)) {
454 (Some(x), Some(y)) => match x.partial_cmp(&y) {
455 Some(o) => o,
456 None => return false, },
458 _ => a.cmp(b),
459 };
460 match op {
461 Op::Eq => ord == Ordering::Equal,
462 Op::Ne => ord != Ordering::Equal,
463 Op::Lt => ord == Ordering::Less,
464 Op::Le => ord != Ordering::Greater,
465 Op::Gt => ord == Ordering::Greater,
466 Op::Ge => ord != Ordering::Less,
467 }
468}
469
470#[derive(Debug, Clone)]
472#[must_use]
473#[non_exhaustive]
474pub enum QueryOutput {
475 Select(Vec<String>, Vec<Binding>),
477 Ask(bool),
479 Construct(Vec<(String, String, String)>),
481}
482
483#[derive(Debug, Clone, PartialEq, Eq)]
486#[must_use]
487#[non_exhaustive]
488pub enum SummaryQueryShape {
489 PredicateCount { predicate: String, variable: String },
491 TripleCount { variable: String },
493 PredicateTotals {
495 predicate_variable: String,
496 count_variable: String,
497 },
498 PredicateList { variable: String },
500 PredicateDistinctCount { variable: String },
502 TripleExists,
504 PredicateExists { predicate: String },
506}
507
508#[derive(Debug, Clone, PartialEq, Eq)]
512pub struct RoutedTriplePattern {
513 pub subject: Option<String>,
514 pub predicate: Option<String>,
515 pub object: Option<String>,
516}
517
518pub fn routed_triple_pattern(query: &str) -> Result<Option<RoutedTriplePattern>, SparqlError> {
523 let parsed = parse_query(query)?;
524 let sel = match parsed {
525 Query::Select {
526 pattern, dataset, ..
527 } => lower_select(&pattern, &dataset)?,
528 Query::Ask { pattern, .. } => lower_pattern(&pattern)?,
529 Query::Construct { pattern, .. } => lower_pattern(&pattern)?,
530 Query::Describe { pattern, .. } => lower_pattern(&pattern)?,
531 };
532 if !sel.from.is_empty() || sel.from_named.is_some() {
533 return Ok(None);
534 }
535 let Plan::Bgp(patterns) = sel.plan else {
536 return Ok(None);
537 };
538 let [tp] = patterns.as_slice() else {
539 return Ok(None);
540 };
541 Ok(Some(RoutedTriplePattern {
542 subject: term_const(&tp.s),
543 predicate: term_const(&tp.p),
544 object: term_const(&tp.o),
545 }))
546}
547
548fn term_const(term: &PatternTerm) -> Option<String> {
549 match term {
550 PatternTerm::Const(t) => Some(t.clone()),
551 PatternTerm::Var(_) => None,
552 }
553}
554
555pub fn summary_query_shape(query: &str) -> Result<Option<SummaryQueryShape>, SparqlError> {
562 let parsed = parse_query(query)?;
563 match parsed {
564 Query::Select {
565 pattern, dataset, ..
566 } => {
567 let sel = lower_select(&pattern, &dataset)?;
568 if !sel.from.is_empty()
569 || sel.from_named.is_some()
570 || sel.offset != 0
571 || sel.limit.is_some()
572 || !sel.order.is_empty()
573 || !sel.having.is_empty()
574 {
575 return Ok(None);
576 }
577 if sel.distinct {
578 if sel.group.is_some() || !sel.extends.is_empty() {
579 return Ok(None);
580 }
581 let [projected] = sel.project.as_slice() else {
582 return Ok(None);
583 };
584 let Some(SummaryPatternShape::AnyPredicate { variable }) =
585 single_summary_pattern(&sel.plan)
586 else {
587 return Ok(None);
588 };
589 return if projected == &variable {
590 Ok(Some(SummaryQueryShape::PredicateList { variable }))
591 } else {
592 Ok(None)
593 };
594 }
595 let Some(group) = &sel.group else {
596 return Ok(None);
597 };
598 if group.aggs.len() != 1 {
599 return Ok(None);
600 }
601 match group.by.as_slice() {
602 [] => match &group.aggs[0].1 {
603 Agg::CountStar { distinct: false } => {
604 let Some(variable) = public_aggregate_variable(&sel, &group.aggs[0].0)
605 else {
606 return Ok(None);
607 };
608 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
609 SummaryPatternShape::Predicate(predicate) => {
610 SummaryQueryShape::PredicateCount {
611 predicate,
612 variable,
613 }
614 }
615 SummaryPatternShape::AnyPredicate { .. } => {
616 SummaryQueryShape::TripleCount { variable }
617 }
618 }))
619 }
620 Agg::Count(counted, true) => {
621 let Some(public_variable) =
622 public_aggregate_variable(&sel, &group.aggs[0].0)
623 else {
624 return Ok(None);
625 };
626 let Some(SummaryPatternShape::AnyPredicate { variable }) =
627 single_summary_pattern(&sel.plan)
628 else {
629 return Ok(None);
630 };
631 if &variable != counted {
632 return Ok(None);
633 }
634 Ok(Some(SummaryQueryShape::PredicateDistinctCount {
635 variable: public_variable,
636 }))
637 }
638 _ => Ok(None),
639 },
640 [group_var] => {
641 if !matches!(group.aggs[0].1, Agg::CountStar { distinct: false }) {
642 return Ok(None);
643 }
644 let Some(SummaryPatternShape::AnyPredicate { variable }) =
645 single_summary_pattern(&sel.plan)
646 else {
647 return Ok(None);
648 };
649 if &variable != group_var {
650 return Ok(None);
651 }
652 let Some(count_variable) =
653 public_group_aggregate_variable(&sel, &group.aggs[0].0, group_var)
654 else {
655 return Ok(None);
656 };
657 Ok(Some(SummaryQueryShape::PredicateTotals {
658 predicate_variable: group_var.clone(),
659 count_variable,
660 }))
661 }
662 _ => Ok(None),
663 }
664 }
665 Query::Ask { pattern, .. } => {
666 let sel = lower_pattern(&pattern)?;
667 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
668 SummaryPatternShape::Predicate(predicate) => {
669 SummaryQueryShape::PredicateExists { predicate }
670 }
671 SummaryPatternShape::AnyPredicate { .. } => SummaryQueryShape::TripleExists,
672 }))
673 }
674 Query::Construct { .. } | Query::Describe { .. } => Ok(None),
675 }
676}
677
678enum SummaryPatternShape {
679 Predicate(String),
680 AnyPredicate { variable: String },
681}
682
683fn single_summary_pattern(plan: &Plan) -> Option<SummaryPatternShape> {
684 let Plan::Bgp(patterns) = plan else {
685 return None;
686 };
687 let [tp] = patterns.as_slice() else {
688 return None;
689 };
690 let (PatternTerm::Var(s), PatternTerm::Var(o)) = (&tp.s, &tp.o) else {
691 return None;
692 };
693 if s == o {
694 return None;
695 }
696 match &tp.p {
697 PatternTerm::Const(p) => Some(SummaryPatternShape::Predicate(p.clone())),
698 PatternTerm::Var(p) if p != s && p != o => Some(SummaryPatternShape::AnyPredicate {
699 variable: p.clone(),
700 }),
701 _ => None,
702 }
703}
704
705fn public_aggregate_variable(sel: &Select, aggregate_var: &str) -> Option<String> {
706 let [projected] = sel.project.as_slice() else {
707 return None;
708 };
709 if projected == aggregate_var {
710 return Some(projected.clone());
711 }
712 sel.extends.iter().find_map(|(var, expr)| match expr {
713 FExpr::Var(source) if var == projected && source == aggregate_var => Some(var.clone()),
714 _ => None,
715 })
716}
717
718fn public_group_aggregate_variable(
719 sel: &Select,
720 aggregate_var: &str,
721 group_var: &str,
722) -> Option<String> {
723 let [projected_group, projected_aggregate] = sel.project.as_slice() else {
724 return None;
725 };
726 if projected_group != group_var {
727 return None;
728 }
729 if projected_aggregate == aggregate_var {
730 return Some(projected_aggregate.clone());
731 }
732 sel.extends.iter().find_map(|(var, expr)| match expr {
733 FExpr::Var(source) if var == projected_aggregate && source == aggregate_var => {
734 Some(var.clone())
735 }
736 _ => None,
737 })
738}
739
740#[derive(Debug, Clone, Copy)]
743pub struct CommunityPartial {
744 pub community: usize,
745 pub subjects: usize,
746 pub rows: usize,
747}
748
749pub type CommunitySelect = (Vec<String>, Vec<Binding>, Vec<CommunityPartial>);
752
753pub fn eval_select_communities(
764 rete: &Rete,
765 query: &str,
766 round: Option<usize>,
767) -> Result<CommunitySelect, SparqlError> {
768 let parsed = parse_query(query)?;
769 let out = match parsed {
770 Query::Select {
771 pattern, dataset, ..
772 } => run_select_communities(rete, &lower_select(&pattern, &dataset)?, round),
773 _ => Err(SparqlError::Unsupported(
774 "community-split evaluation supports SELECT queries only",
775 )),
776 };
777 match rete.take_service_error() {
780 Some(e) => Err(SparqlError::Service(e)),
781 None => out,
782 }
783}
784
785pub fn eval_query(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
787 eval_query_opts(rete, query, false)
788}
789
790pub fn eval_query_reasoned(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
795 eval_query_opts(rete, query, true)
796}
797
798fn eval_query_opts(rete: &Rete, query: &str, reason: bool) -> Result<QueryOutput, SparqlError> {
799 let out = eval_query_inner(rete, query, reason);
800 match rete.take_service_error() {
804 Some(e) => Err(SparqlError::Service(e)),
805 None => out,
806 }
807}
808
809fn maybe_reason(rete: &Rete, mut sel: Select, reason: bool) -> Select {
812 if reason {
813 let projected = sel.project.clone();
814 sel.plan = ql::reason_rewrite(sel.plan, rete, &projected);
815 }
816 sel
817}
818
819fn eval_query_inner(rete: &Rete, query: &str, reason: bool) -> Result<QueryOutput, SparqlError> {
820 let parsed = parse_query(query)?;
821 match parsed {
822 Query::Select {
823 pattern, dataset, ..
824 } => {
825 let (vars, rows) = run_select(
826 rete,
827 &maybe_reason(rete, lower_select(&pattern, &dataset)?, reason),
828 );
829 Ok(QueryOutput::Select(vars, rows))
830 }
831 Query::Ask { pattern, .. } => {
832 let sel = maybe_reason(rete, lower_pattern(&pattern)?, reason);
833 Ok(QueryOutput::Ask(ask_solution(rete, &sel)))
834 }
835 Query::Construct {
836 template, pattern, ..
837 } => {
838 let sel = maybe_reason(rete, lower_pattern(&pattern)?, reason);
839 let (ctx, sols) = raw_solutions(rete, &sel);
840 Ok(QueryOutput::Construct(instantiate(&ctx, &template, &sols)))
841 }
842 Query::Describe {
843 pattern, dataset, ..
844 } => {
845 let sel = maybe_reason(rete, lower_select(&pattern, &dataset)?, reason);
848 let (ctx, rows) = raw_solutions(rete, &sel);
849 let mut resources = std::collections::BTreeSet::new();
850 for row in &rows {
851 if sel.project.is_empty() {
852 for val in row.iter().flatten() {
853 if let Some(t) = ctx.resolver.str_of(val) {
854 resources.insert(t.to_string());
855 }
856 }
857 } else {
858 for v in &sel.project {
859 if let Some(val) = ctx.slots.slot(v).and_then(|s| row[s].as_ref()) {
860 if let Some(t) = ctx.resolver.str_of(val) {
861 resources.insert(t.to_string());
862 }
863 }
864 }
865 }
866 }
867 let mut triples = std::collections::BTreeSet::new();
868 for r in &resources {
869 for t in rete.query(Some(r), None, None) {
870 triples.insert(t);
871 }
872 }
873 Ok(QueryOutput::Construct(triples.into_iter().collect()))
874 }
875 }
876}
877
878pub fn eval_sparql(rete: &Rete, query: &str) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
882 eval_sparql_opts(rete, query, false)
883}
884
885pub fn eval_sparql_reasoned(
887 rete: &Rete,
888 query: &str,
889) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
890 eval_sparql_opts(rete, query, true)
891}
892
893fn eval_sparql_opts(
894 rete: &Rete,
895 query: &str,
896 reason: bool,
897) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
898 let sel = maybe_reason(rete, parse_select(query)?, reason);
899 let out = run_select(rete, &sel);
900 match rete.take_service_error() {
902 Some(e) => Err(SparqlError::Service(e)),
903 None => Ok(out),
904 }
905}
906
907pub(crate) fn fmt_num_typed(x: f64) -> String {
913 if x.fract() == 0.0 {
914 format!(
915 "\"{}\"^^<http://www.w3.org/2001/XMLSchema#integer>",
916 x as i64
917 )
918 } else {
919 let cleaned: f64 = format!("{x:.14e}").parse().unwrap_or(x);
923 format!("\"{cleaned}\"^^<http://www.w3.org/2001/XMLSchema#decimal>")
924 }
925}
926
927fn reverse(ast: PathAst) -> PathAst {
929 match ast {
930 PathAst::Pred(p, r) => PathAst::Pred(p, !r),
931 PathAst::Rep(inner, rep) => PathAst::Rep(Box::new(reverse(*inner)), rep),
932 PathAst::Seq(a, b) => PathAst::Seq(Box::new(reverse(*b)), Box::new(reverse(*a))),
933 PathAst::Alt(a, b) => PathAst::Alt(Box::new(reverse(*a)), Box::new(reverse(*b))),
934 PathAst::NegatedSet(s, r) => PathAst::NegatedSet(s, !r),
935 }
936}
937
938#[cfg(test)]
939mod tests {
940 use super::*;
941 use crate::dictionary::DictionaryBuilder;
942 use crate::index::GraphIndexBuilder;
943 use crate::write_file;
944
945 fn rete_from(triples: &[(&str, &str, &str)]) -> Vec<u8> {
946 let mut db = DictionaryBuilder::new();
947 for (s, p, o) in triples {
948 db.observe(s, p, o);
949 }
950 let dict = db.build();
951 let mut ib = GraphIndexBuilder::new();
952 for (s, p, o) in triples {
953 ib.push(dict.encode(s, p, o).unwrap());
954 }
955 write_file(&dict, &ib.build(), false, &[], 0)
956 }
957
958 #[test]
959 fn parses_select_with_two_patterns() {
960 let q = r#"
961 PREFIX ex: <http://ex/>
962 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }
963 "#;
964 let sel = parse_select(q).unwrap();
965 assert_eq!(sel.project, vec!["x", "z"]);
966 match &sel.plan {
967 Plan::Bgp(p) => assert_eq!(p.len(), 2),
968 other => panic!("expected a BGP plan, got {other:?}"),
969 }
970 }
971
972 #[test]
973 fn evaluates_two_hop_select() {
974 let bytes = rete_from(&[
975 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
976 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
977 ]);
978 let rete = Rete::open(&bytes).unwrap();
979 let q = r#"PREFIX ex: <http://ex/>
980 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }"#;
981 let (proj, sols) = eval_sparql(&rete, q).unwrap();
982 assert_eq!(proj, vec!["x", "z"]);
983 assert_eq!(sols.len(), 1);
984 assert_eq!(sols[0]["x"], "<http://ex/Alice>");
985 assert_eq!(sols[0]["z"], "<http://ex/Carol>");
986 }
987
988 #[test]
989 fn union_returns_both_sides() {
990 let bytes = rete_from(&[
991 ("<http://ex/Alice>", "<http://ex/likes>", "<http://ex/Tea>"),
992 ("<http://ex/Bob>", "<http://ex/hates>", "<http://ex/Tea>"),
993 ]);
994 let rete = Rete::open(&bytes).unwrap();
995 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { \
996 { ?p ex:likes ex:Tea } UNION { ?p ex:hates ex:Tea } }";
997 let (_, sols) = eval_sparql(&rete, q).unwrap();
998 let mut who: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
999 who.sort();
1000 assert_eq!(who, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1001 }
1002
1003 #[test]
1004 fn optional_keeps_left_when_right_absent() {
1005 let bytes = rete_from(&[
1007 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1008 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1009 ("<http://ex/Alice>", "<http://ex/email>", "\"a@ex\""),
1010 ]);
1011 let rete = Rete::open(&bytes).unwrap();
1012 let q = "PREFIX ex: <http://ex/> SELECT ?p ?e WHERE { \
1013 ?p ex:name ?n . OPTIONAL { ?p ex:email ?e } }";
1014 let (_, sols) = eval_sparql(&rete, q).unwrap();
1015 assert_eq!(sols.len(), 2, "both people present");
1016 let alice = sols.iter().find(|b| b["p"] == "<http://ex/Alice>").unwrap();
1017 assert_eq!(alice["e"], "\"a@ex\"");
1018 let bob = sols.iter().find(|b| b["p"] == "<http://ex/Bob>").unwrap();
1019 assert!(!bob.contains_key("e"), "Bob has no email binding");
1020 }
1021
1022 #[test]
1023 fn numeric_filter_on_typed_literal() {
1024 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
1026 let bytes = rete_from(&[
1027 (
1028 "<http://ex/Alice>",
1029 "<http://ex/age>",
1030 &format!("\"30\"^^{xsd}"),
1031 ),
1032 (
1033 "<http://ex/Bob>",
1034 "<http://ex/age>",
1035 &format!("\"25\"^^{xsd}"),
1036 ),
1037 ]);
1038 let rete = Rete::open(&bytes).unwrap();
1039 let q = "PREFIX ex: <http://ex/> \
1040 SELECT ?p WHERE { ?p ex:age ?age . FILTER(?age > 27) }";
1041 let (_, sols) = eval_sparql(&rete, q).unwrap();
1042 assert_eq!(sols.len(), 1);
1043 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1044 }
1045
1046 #[test]
1047 fn filter_equality_and_boolean_logic() {
1048 let bytes = rete_from(&[
1049 ("<http://ex/Alice>", "<http://ex/city>", "<http://ex/NYC>"),
1050 ("<http://ex/Bob>", "<http://ex/city>", "<http://ex/LA>"),
1051 ("<http://ex/Carol>", "<http://ex/city>", "<http://ex/NYC>"),
1052 ]);
1053 let rete = Rete::open(&bytes).unwrap();
1054 let q = "PREFIX ex: <http://ex/> \
1055 SELECT ?p WHERE { ?p ex:city ?c . FILTER(?c = ex:NYC && ?p != ex:Carol) }";
1056 let (_, sols) = eval_sparql(&rete, q).unwrap();
1057 assert_eq!(sols.len(), 1);
1058 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1059 }
1060
1061 #[test]
1062 fn distinct_collapses_duplicate_projections() {
1063 let bytes = rete_from(&[
1065 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1066 (
1067 "<http://ex/Alice>",
1068 "<http://ex/knows>",
1069 "<http://ex/Carol>",
1070 ),
1071 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1072 ("<http://ex/Carol>", "<http://ex/knows>", "<http://ex/Dave>"),
1073 ]);
1074 let rete = Rete::open(&bytes).unwrap();
1075 let base = "PREFIX ex: <http://ex/> SELECT {} ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1076
1077 let (_, non_distinct) = eval_sparql(&rete, &base.replace("{}", "")).unwrap();
1078 assert_eq!(non_distinct.len(), 2); let (proj, distinct) = eval_sparql(&rete, &base.replace("{}", "DISTINCT")).unwrap();
1081 assert_eq!(proj, vec!["z"]);
1082 assert_eq!(distinct.len(), 1);
1083 assert_eq!(distinct[0]["z"], "<http://ex/Dave>");
1084 assert!(!distinct[0].contains_key("x"));
1086 }
1087
1088 #[test]
1089 fn transitive_path_reachability() {
1090 let bytes = rete_from(&[
1092 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
1093 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
1094 ("<http://ex/C>", "<http://ex/k>", "<http://ex/D>"),
1095 ]);
1096 let rete = Rete::open(&bytes).unwrap();
1097
1098 let q = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k+ ?y }";
1100 let (_, sols) = eval_sparql(&rete, q).unwrap();
1101 let mut ys: Vec<&str> = sols.iter().map(|b| b["y"].as_str()).collect();
1102 ys.sort();
1103 assert_eq!(ys, vec!["<http://ex/B>", "<http://ex/C>", "<http://ex/D>"]);
1104
1105 let q0 = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k* ?y }";
1107 let (_, s0) = eval_sparql(&rete, q0).unwrap();
1108 assert!(s0.iter().any(|b| b["y"] == "<http://ex/A>"));
1109 assert_eq!(s0.len(), 4); }
1111
1112 #[test]
1113 fn sequence_and_alternative_paths() {
1114 let bytes = rete_from(&[
1116 ("<http://ex/Alice>", "<http://ex/parent>", "<http://ex/Bob>"),
1117 ("<http://ex/Bob>", "<http://ex/parent>", "<http://ex/Carol>"),
1118 (
1119 "<http://ex/Alice>",
1120 "<http://ex/stepparent>",
1121 "<http://ex/Dave>",
1122 ),
1123 ]);
1124 let rete = Rete::open(&bytes).unwrap();
1125
1126 let q = "PREFIX ex: <http://ex/> SELECT ?g WHERE { ex:Alice ex:parent/ex:parent ?g }";
1128 let (_, sols) = eval_sparql(&rete, q).unwrap();
1129 assert_eq!(sols.len(), 1);
1130 assert_eq!(sols[0]["g"], "<http://ex/Carol>");
1131
1132 let qa = "PREFIX ex: <http://ex/> \
1134 SELECT ?p WHERE { ex:Alice ex:parent|ex:stepparent ?p }";
1135 let (_, sa) = eval_sparql(&rete, qa).unwrap();
1136 let mut ps: Vec<&str> = sa.iter().map(|b| b["p"].as_str()).collect();
1137 ps.sort();
1138 assert_eq!(ps, vec!["<http://ex/Bob>", "<http://ex/Dave>"]);
1139 }
1140
1141 #[test]
1142 fn group_concat_aggregate() {
1143 let bytes = rete_from(&[
1144 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1145 (
1146 "<http://ex/Alice>",
1147 "<http://ex/knows>",
1148 "<http://ex/Carol>",
1149 ),
1150 ]);
1151 let rete = Rete::open(&bytes).unwrap();
1152 let q = "PREFIX ex: <http://ex/> \
1153 SELECT (GROUP_CONCAT(?f; SEPARATOR=\"|\") AS ?fs) WHERE { ex:Alice ex:knows ?f }";
1154 let (_, sols) = eval_sparql(&rete, q).unwrap();
1155 assert_eq!(sols.len(), 1);
1156 let fs = sols[0]["fs"].trim_matches('"');
1158 let mut parts: Vec<&str> = fs.split('|').collect();
1159 parts.sort();
1160 assert_eq!(parts, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1161 }
1162
1163 #[test]
1164 fn group_by_having() {
1165 let bytes = rete_from(&[
1167 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1168 (
1169 "<http://ex/Alice>",
1170 "<http://ex/knows>",
1171 "<http://ex/Carol>",
1172 ),
1173 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1174 ]);
1175 let rete = Rete::open(&bytes).unwrap();
1176 let q = "PREFIX ex: <http://ex/> SELECT ?p (COUNT(?f) AS ?n) \
1178 WHERE { ?p ex:knows ?f } GROUP BY ?p HAVING (COUNT(?f) > 1)";
1179 let (_, sols) = eval_sparql(&rete, q).unwrap();
1180 assert_eq!(sols.len(), 1);
1181 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1182 assert_eq!(
1183 sols[0]["n"],
1184 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1185 );
1186 }
1187
1188 #[test]
1189 fn count_group_by() {
1190 let bytes = rete_from(&[
1192 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1193 (
1194 "<http://ex/Alice>",
1195 "<http://ex/knows>",
1196 "<http://ex/Carol>",
1197 ),
1198 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1199 ]);
1200 let rete = Rete::open(&bytes).unwrap();
1201 let q = "PREFIX ex: <http://ex/> \
1202 SELECT ?p (COUNT(?f) AS ?n) WHERE { ?p ex:knows ?f } GROUP BY ?p";
1203 let (_, sols) = eval_sparql(&rete, q).unwrap();
1204 let mut counts: Vec<(String, String)> = sols
1205 .iter()
1206 .map(|b| (b["p"].clone(), b["n"].clone()))
1207 .collect();
1208 counts.sort();
1209 assert_eq!(
1210 counts,
1211 vec![
1212 (
1213 "<http://ex/Alice>".into(),
1214 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1215 ),
1216 (
1217 "<http://ex/Bob>".into(),
1218 "\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1219 ),
1220 ]
1221 );
1222 }
1223
1224 #[test]
1225 fn global_count_star() {
1226 let bytes = rete_from(&[
1227 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
1228 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
1229 ]);
1230 let rete = Rete::open(&bytes).unwrap();
1231 let (_, sols) = eval_sparql(&rete, "SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1232 assert_eq!(sols.len(), 1);
1233 assert_eq!(
1234 sols[0]["n"],
1235 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1236 );
1237 }
1238
1239 #[test]
1240 fn summary_query_shape_classifies_only_exact_predicate_totals() {
1241 let count = summary_query_shape(
1242 "PREFIX ex: <http://ex/> SELECT (COUNT(*) AS ?n) WHERE { ?s ex:p ?o }",
1243 )
1244 .unwrap();
1245 assert_eq!(
1246 count,
1247 Some(SummaryQueryShape::PredicateCount {
1248 predicate: "<http://ex/p>".into(),
1249 variable: "n".into(),
1250 })
1251 );
1252
1253 let total = summary_query_shape("SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1254 assert_eq!(
1255 total,
1256 Some(SummaryQueryShape::TripleCount {
1257 variable: "n".into(),
1258 })
1259 );
1260
1261 let by_pred =
1262 summary_query_shape("SELECT ?p (COUNT(*) AS ?n) WHERE { ?s ?p ?o } GROUP BY ?p")
1263 .unwrap();
1264 assert_eq!(
1265 by_pred,
1266 Some(SummaryQueryShape::PredicateTotals {
1267 predicate_variable: "p".into(),
1268 count_variable: "n".into(),
1269 })
1270 );
1271
1272 let predicates = summary_query_shape("SELECT DISTINCT ?p WHERE { ?s ?p ?o }").unwrap();
1273 assert_eq!(
1274 predicates,
1275 Some(SummaryQueryShape::PredicateList {
1276 variable: "p".into(),
1277 })
1278 );
1279
1280 let predicate_count =
1281 summary_query_shape("SELECT (COUNT(DISTINCT ?p) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1282 assert_eq!(
1283 predicate_count,
1284 Some(SummaryQueryShape::PredicateDistinctCount {
1285 variable: "n".into(),
1286 })
1287 );
1288
1289 let ask = summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o }").unwrap();
1290 assert_eq!(
1291 ask,
1292 Some(SummaryQueryShape::PredicateExists {
1293 predicate: "<http://ex/p>".into(),
1294 })
1295 );
1296
1297 let any_ask = summary_query_shape("ASK { ?s ?p ?o }").unwrap();
1298 assert_eq!(any_ask, Some(SummaryQueryShape::TripleExists));
1299
1300 let constrained =
1301 summary_query_shape("PREFIX ex: <http://ex/> ASK { ex:a ex:p ?o }").unwrap();
1302 assert_eq!(constrained, None);
1303
1304 let filtered =
1305 summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o FILTER(?s = ?o) }")
1306 .unwrap();
1307 assert_eq!(filtered, None);
1308 }
1309
1310 #[test]
1311 fn filter_exists_and_not_exists() {
1312 let bytes = rete_from(&[
1314 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1315 (
1316 "<http://ex/Alice>",
1317 "<http://ex/knows>",
1318 "<http://ex/Carol>",
1319 ),
1320 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1321 ]);
1322 let rete = Rete::open(&bytes).unwrap();
1323
1324 let q_exists = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1326 ex:Alice ex:knows ?f . FILTER EXISTS { ?f ex:knows ?x } }";
1327 let (_, e) = eval_sparql(&rete, q_exists).unwrap();
1328 assert_eq!(
1329 e.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1330 vec!["<http://ex/Bob>"]
1331 );
1332
1333 let q_not = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1335 ex:Alice ex:knows ?f . FILTER NOT EXISTS { ?f ex:knows ?x } }";
1336 let (_, n) = eval_sparql(&rete, q_not).unwrap();
1337 assert_eq!(
1338 n.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1339 vec!["<http://ex/Carol>"]
1340 );
1341 }
1342
1343 #[test]
1344 fn ask_repeated_variable_pattern() {
1345 let yes = rete_from(&[
1348 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1349 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Bob>"),
1350 ]);
1351 let rete = Rete::open(&yes).unwrap();
1352 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1353 QueryOutput::Ask(b) => assert!(b, "Bob knows himself"),
1354 other => panic!("expected Ask, got {other:?}"),
1355 }
1356 let no = rete_from(&[("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>")]);
1359 let rete = Rete::open(&no).unwrap();
1360 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1361 QueryOutput::Ask(b) => assert!(!b, "nobody knows themselves"),
1362 other => panic!("expected Ask, got {other:?}"),
1363 }
1364 }
1365
1366 #[test]
1367 fn minus_excludes_compatible() {
1368 let bytes = rete_from(&[
1370 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1371 (
1372 "<http://ex/Alice>",
1373 "<http://ex/knows>",
1374 "<http://ex/Carol>",
1375 ),
1376 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1377 ]);
1378 let rete = Rete::open(&bytes).unwrap();
1379 let q = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1381 ex:Alice ex:knows ?f . MINUS { ?f ex:knows ?x } }";
1382 let (_, sols) = eval_sparql(&rete, q).unwrap();
1383 let fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1384 assert_eq!(fs, vec!["<http://ex/Carol>"]);
1385 }
1386
1387 #[test]
1388 fn filter_exists_disjoint_variable() {
1389 let bytes = rete_from(&[
1394 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1395 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1396 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1397 ]);
1398 let rete = Rete::open(&bytes).unwrap();
1399 let q_none = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1400 ?x ex:a ex:Person FILTER NOT EXISTS { ?y ex:a ex:Drink } }";
1401 assert!(eval_sparql(&rete, q_none).unwrap().1.is_empty());
1402 let q_all = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1403 ?x ex:a ex:Person FILTER EXISTS { ?y ex:a ex:Drink } }";
1404 assert_eq!(eval_sparql(&rete, q_all).unwrap().1.len(), 2);
1405 }
1406
1407 #[test]
1408 fn minus_disjoint_domain_keeps_all() {
1409 let bytes = rete_from(&[
1413 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1414 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1415 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1416 ]);
1417 let rete = Rete::open(&bytes).unwrap();
1418 let q = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1419 ?x ex:a ex:Person MINUS { ?y ex:a ex:Drink } }";
1420 let (_, sols) = eval_sparql(&rete, q).unwrap();
1421 let mut xs: Vec<&str> = sols.iter().map(|b| b["x"].as_str()).collect();
1422 xs.sort();
1423 assert_eq!(xs, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1424 }
1425
1426 #[test]
1427 fn values_pushdown_selects_subset() {
1428 let bytes = rete_from(&[
1431 ("<http://ex/a>", "<http://ex/d>", "<http://ex/Bio>"),
1432 ("<http://ex/b>", "<http://ex/d>", "<http://ex/Phys>"),
1433 ("<http://ex/c>", "<http://ex/d>", "<http://ex/Chem>"),
1434 ]);
1435 let rete = Rete::open(&bytes).unwrap();
1436 let q = "PREFIX ex: <http://ex/> SELECT ?p ?disc WHERE { \
1437 VALUES ?disc { ex:Bio ex:Phys } ?p ex:d ?disc }";
1438 let (_, sols) = eval_sparql(&rete, q).unwrap();
1439 let mut got: Vec<(String, String)> = sols
1440 .iter()
1441 .map(|b| (b["p"].clone(), b["disc"].clone()))
1442 .collect();
1443 got.sort();
1444 assert_eq!(
1445 got,
1446 vec![
1447 ("<http://ex/a>".into(), "<http://ex/Bio>".into()),
1448 ("<http://ex/b>".into(), "<http://ex/Phys>".into()),
1449 ]
1450 );
1451 }
1452
1453 #[test]
1454 fn values_inline_data_joins() {
1455 let bytes = rete_from(&[
1456 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1457 (
1458 "<http://ex/Alice>",
1459 "<http://ex/knows>",
1460 "<http://ex/Carol>",
1461 ),
1462 ("<http://ex/Dave>", "<http://ex/knows>", "<http://ex/Eve>"),
1463 ]);
1464 let rete = Rete::open(&bytes).unwrap();
1465 let q = "PREFIX ex: <http://ex/> \
1467 SELECT ?f WHERE { VALUES ?p { ex:Alice } ?p ex:knows ?f }";
1468 let (_, sols) = eval_sparql(&rete, q).unwrap();
1469 let mut fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1470 fs.sort();
1471 assert_eq!(fs, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1472 }
1473
1474 #[test]
1475 fn graph_queries_over_named_graphs() {
1476 use crate::write_dataset;
1477 let mut db = DictionaryBuilder::new();
1479 let edges = [
1480 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1481 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1482 ];
1483 for (s, p, o) in edges {
1484 db.observe(s, p, o);
1485 }
1486 let dict = db.build();
1487 let mut social = GraphIndexBuilder::new();
1488 social.push(dict.encode(edges[0].0, edges[0].1, edges[0].2).unwrap());
1489 let mut profile = GraphIndexBuilder::new();
1490 profile.push(dict.encode(edges[1].0, edges[1].1, edges[1].2).unwrap());
1491 let named = vec![
1492 ("<http://ex/social>".to_string(), social.build()),
1493 ("<http://ex/profile>".to_string(), profile.build()),
1494 ];
1495 let bytes = write_dataset(
1496 &dict,
1497 &GraphIndexBuilder::new().build(),
1498 &named,
1499 true,
1500 &[],
1501 0,
1502 );
1503 let rete = Rete::open(&bytes).unwrap();
1504
1505 let q = "PREFIX ex: <http://ex/> \
1507 SELECT ?f WHERE { GRAPH ex:social { ex:Alice ex:knows ?f } }";
1508 let (_, s) = eval_sparql(&rete, q).unwrap();
1509 assert_eq!(s.len(), 1);
1510 assert_eq!(s[0]["f"], "<http://ex/Bob>");
1511
1512 let q2 = "PREFIX ex: <http://ex/> \
1514 SELECT ?g WHERE { GRAPH ?g { ex:Bob ex:age ?a } }";
1515 let (_, s2) = eval_sparql(&rete, q2).unwrap();
1516 assert_eq!(s2.len(), 1);
1517 assert_eq!(s2[0]["g"], "<http://ex/profile>");
1518
1519 let q3 = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1522 GRAPH ex:profile { ?f ex:age ?a . FILTER EXISTS { ?f ex:age ?a2 } } }";
1523 assert_eq!(eval_sparql(&rete, q3).unwrap().1.len(), 1);
1524 let q4 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
1525 GRAPH ex:social { ?s ex:knows ?o . FILTER NOT EXISTS { ?s ex:age ?a } } }";
1526 assert_eq!(eval_sparql(&rete, q4).unwrap().1.len(), 1);
1528 }
1529
1530 #[test]
1531 fn from_unions_named_graphs() {
1532 use crate::write_dataset;
1533 let mut db = DictionaryBuilder::new();
1536 let t = [
1537 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1538 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1539 ];
1540 for (s, p, o) in t {
1541 db.observe(s, p, o);
1542 }
1543 let dict = db.build();
1544 let mut g1 = GraphIndexBuilder::new();
1545 g1.push(dict.encode(t[0].0, t[0].1, t[0].2).unwrap());
1546 let mut g2 = GraphIndexBuilder::new();
1547 g2.push(dict.encode(t[1].0, t[1].1, t[1].2).unwrap());
1548 let named = vec![
1549 ("<http://ex/social>".to_string(), g1.build()),
1550 ("<http://ex/profile>".to_string(), g2.build()),
1551 ];
1552 let bytes = write_dataset(
1553 &dict,
1554 &GraphIndexBuilder::new().build(),
1555 &named,
1556 true,
1557 &[],
1558 0,
1559 );
1560 let rete = Rete::open(&bytes).unwrap();
1561
1562 let q0 = "PREFIX ex: <http://ex/> SELECT ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1564 assert!(eval_sparql(&rete, q0).unwrap().1.is_empty());
1565
1566 let q = "PREFIX ex: <http://ex/> \
1568 SELECT ?z FROM ex:social FROM ex:profile \
1569 WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1570 let (_, s) = eval_sparql(&rete, q).unwrap();
1571 assert_eq!(s.len(), 1);
1572 assert_eq!(s[0]["z"], "<http://ex/Carol>");
1573
1574 let qn = "PREFIX ex: <http://ex/> SELECT ?g FROM NAMED ex:social \
1576 WHERE { GRAPH ?g { ?x ex:knows ?y } }";
1577 let (_, sn) = eval_sparql(&rete, qn).unwrap();
1578 let gs: Vec<&str> = sn.iter().map(|b| b["g"].as_str()).collect();
1579 assert_eq!(gs, vec!["<http://ex/social>"]); }
1581
1582 #[test]
1583 fn describe_resource() {
1584 let bytes = rete_from(&[
1585 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1586 ("<http://ex/Alice>", "<http://ex/age>", "\"30\""),
1587 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1588 ]);
1589 let rete = Rete::open(&bytes).unwrap();
1590 match eval_query(&rete, "DESCRIBE <http://ex/Alice>").unwrap() {
1592 QueryOutput::Construct(t) => assert_eq!(t.len(), 2),
1593 other => panic!("expected Construct, got {other:?}"),
1594 }
1595 let q = "PREFIX ex: <http://ex/> DESCRIBE ?x WHERE { ?x ex:age ?a }";
1597 match eval_query(&rete, q).unwrap() {
1598 QueryOutput::Construct(t) => {
1599 assert_eq!(t.len(), 3);
1601 }
1602 other => panic!("expected Construct, got {other:?}"),
1603 }
1604 }
1605
1606 #[test]
1607 fn ask_and_construct() {
1608 let bytes = rete_from(&[
1609 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1610 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1611 ]);
1612 let rete = Rete::open(&bytes).unwrap();
1613
1614 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:knows ?b }").unwrap() {
1616 QueryOutput::Ask(b) => assert!(b),
1617 other => panic!("expected Ask, got {other:?}"),
1618 }
1619 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:likes ?b }").unwrap() {
1620 QueryOutput::Ask(b) => assert!(!b),
1621 other => panic!("expected Ask, got {other:?}"),
1622 }
1623
1624 let q = "PREFIX ex: <http://ex/> \
1626 CONSTRUCT { ?b ex:knownBy ?a } WHERE { ?a ex:knows ?b }";
1627 match eval_query(&rete, q).unwrap() {
1628 QueryOutput::Construct(mut triples) => {
1629 triples.sort();
1630 assert_eq!(triples.len(), 2);
1631 assert!(triples.contains(&(
1632 "<http://ex/Bob>".into(),
1633 "<http://ex/knownBy>".into(),
1634 "<http://ex/Alice>".into(),
1635 )));
1636 }
1637 other => panic!("expected Construct, got {other:?}"),
1638 }
1639 }
1640
1641 #[test]
1642 fn substr_strbefore_strafter() {
1643 let bytes = rete_from(&[("<http://ex/a>", "<http://ex/name>", "\"Alice Smith\"")]);
1644 let rete = Rete::open(&bytes).unwrap();
1645 let q = "PREFIX ex: <http://ex/> SELECT ?first ?last ?ini WHERE { \
1646 ?p ex:name ?n . \
1647 BIND(STRBEFORE(?n, \" \") AS ?first) \
1648 BIND(STRAFTER(?n, \" \") AS ?last) \
1649 BIND(SUBSTR(?n, 1, 1) AS ?ini) }";
1650 let (_, sols) = eval_sparql(&rete, q).unwrap();
1651 assert_eq!(sols[0]["first"], "\"Alice\"");
1653 assert_eq!(sols[0]["last"], "\"Smith\"");
1654 assert_eq!(sols[0]["ini"], "\"A\"");
1655 }
1656
1657 #[test]
1658 fn concat_and_coalesce() {
1659 let bytes = rete_from(&[
1661 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1662 ("<http://ex/Alice>", "<http://ex/nick>", "\"Al\""),
1663 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1664 ]);
1665 let rete = Rete::open(&bytes).unwrap();
1666 let q = "PREFIX ex: <http://ex/> SELECT ?label WHERE { \
1668 ?p ex:name ?name . OPTIONAL { ?p ex:nick ?nick } \
1669 BIND(CONCAT(\"@\", COALESCE(?nick, ?name)) AS ?label) }";
1670 let (_, sols) = eval_sparql(&rete, q).unwrap();
1671 let mut labels: Vec<&str> = sols.iter().map(|b| b["label"].as_str()).collect();
1672 labels.sort();
1673 assert_eq!(labels, vec!["\"@Al\"", "\"@Bob\""]);
1674 }
1675
1676 #[test]
1677 fn builtin_functions() {
1678 let bytes = rete_from(&[
1679 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice Smith\""),
1680 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob Jones\""),
1681 ]);
1682 let rete = Rete::open(&bytes).unwrap();
1683 let q = "PREFIX ex: <http://ex/> SELECT ?p ?len WHERE { \
1685 ?p ex:name ?n . FILTER(CONTAINS(?n, \"Smith\")) BIND(STRLEN(?n) AS ?len) }";
1686 let (_, sols) = eval_sparql(&rete, q).unwrap();
1687 assert_eq!(sols.len(), 1);
1688 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1689 assert_eq!(
1690 sols[0]["len"],
1691 "\"11\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1692 ); }
1694
1695 #[test]
1696 fn geosparql_filter_and_functions() {
1697 let wkt = "\"POLYGON((0 0,10 0,10 10,0 10,0 0))\"^^\
1698 <http://www.opengis.net/ont/geosparql#wktLiteral>";
1699 let bytes = rete_from(&[
1700 (
1701 "<http://ex/f>",
1702 "<http://www.opengis.net/ont/geosparql#hasGeometry>",
1703 "<http://ex/f/g>",
1704 ),
1705 (
1706 "<http://ex/f/g>",
1707 "<http://www.opengis.net/ont/geosparql#asWKT>",
1708 wkt,
1709 ),
1710 (
1711 "<http://ex/f>",
1712 "<http://ex/year>",
1713 "\"1815\"^^<http://www.w3.org/2001/XMLSchema#integer>",
1714 ),
1715 ]);
1716 let rete = Rete::open(&bytes).unwrap();
1717 let pre = "PREFIX geo: <http://www.opengis.net/ont/geosparql#> \
1718 PREFIX geof: <http://www.opengis.net/def/function/geosparql/> \
1719 PREFIX uom: <http://www.opengis.net/def/uom/OGC/1.0/> PREFIX ex: <http://ex/> ";
1720
1721 let (_, s) = eval_sparql(
1723 &rete,
1724 &format!(
1725 "{pre}SELECT ?f WHERE {{ ?f ex:year ?y ; \
1726 geo:hasGeometry/geo:asWKT ?w . \
1727 FILTER(?y = 1815 && geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral)) }}"
1728 ),
1729 )
1730 .unwrap();
1731 assert_eq!(s.len(), 1, "point inside the polygon in year 1815");
1732 assert_eq!(s[0]["f"], "<http://ex/f>");
1733
1734 let (_, s) = eval_sparql(
1736 &rete,
1737 &format!(
1738 "{pre}SELECT ?f WHERE {{ \
1739 ?f geo:hasGeometry/geo:asWKT ?w . \
1740 FILTER(geof:sfContains(?w, \"POINT(50 50)\"^^geo:wktLiteral)) }}"
1741 ),
1742 )
1743 .unwrap();
1744 assert!(s.is_empty());
1745
1746 let (_, s) = eval_sparql(
1748 &rete,
1749 &format!(
1750 "{pre}SELECT ?f WHERE {{ \
1751 ?f geo:hasGeometry/geo:asWKT ?w . \
1752 FILTER(geof:sfWithin(\"POINT(5 5)\"^^geo:wktLiteral, ?w)) }}"
1753 ),
1754 )
1755 .unwrap();
1756 assert_eq!(s.len(), 1);
1757
1758 let (_, s) = eval_sparql(
1760 &rete,
1761 &format!(
1762 "{pre}SELECT ?f WHERE {{ \
1763 ?f geo:hasGeometry/geo:asWKT ?w . \
1764 FILTER(geof:sfContains(?w, \"garbage\"^^geo:wktLiteral)) }}"
1765 ),
1766 )
1767 .unwrap();
1768 assert!(s.is_empty());
1769
1770 let (_, s) = eval_sparql(
1772 &rete,
1773 &format!(
1774 "{pre}SELECT ?hit WHERE {{ \
1775 ?f geo:hasGeometry/geo:asWKT ?w . \
1776 BIND(geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral) AS ?hit) }}"
1777 ),
1778 )
1779 .unwrap();
1780 assert_eq!(
1781 s[0]["hit"],
1782 "\"true\"^^<http://www.w3.org/2001/XMLSchema#boolean>"
1783 );
1784
1785 let (_, s) = eval_sparql(
1787 &rete,
1788 &format!(
1789 "{pre}SELECT ?d WHERE {{ BIND(geof:distance(\
1790 \"POINT(0 0)\"^^geo:wktLiteral, \"POINT(0 1)\"^^geo:wktLiteral, uom:metre) AS ?d) }}"
1791 ),
1792 )
1793 .unwrap();
1794 assert!(
1795 s[0]["d"].ends_with("XMLSchema#double>"),
1796 "distance is xsd:double: {}",
1797 s[0]["d"]
1798 );
1799 let (_, s) = eval_sparql(
1800 &rete,
1801 &format!(
1802 "{pre}SELECT ?e WHERE {{ \
1803 ?f geo:hasGeometry/geo:asWKT ?w . BIND(geof:envelope(?w) AS ?e) }}"
1804 ),
1805 )
1806 .unwrap();
1807 assert!(s[0]["e"].contains("wktLiteral") && s[0]["e"].contains("POLYGON"));
1808
1809 assert!(eval_sparql(
1811 &rete,
1812 &format!(
1813 "{pre}SELECT ?x WHERE {{ \
1814 BIND(geof:buffer(\"POINT(0 0)\"^^geo:wktLiteral, 1) AS ?x) }}"
1815 )
1816 )
1817 .is_err());
1818 }
1819
1820 #[test]
1821 fn bind_arithmetic() {
1822 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
1823 let bytes = rete_from(&[(
1824 "<http://ex/a>",
1825 "<http://ex/age>",
1826 &format!("\"30\"^^{xsd}"),
1827 )]);
1828 let rete = Rete::open(&bytes).unwrap();
1829 let q = "PREFIX ex: <http://ex/> \
1831 SELECT ?next WHERE { ?p ex:age ?age . BIND(?age + 1 AS ?next) FILTER(?age * 2 > 50) }";
1832 let (_, sols) = eval_sparql(&rete, q).unwrap();
1833 assert_eq!(sols.len(), 1);
1834 assert_eq!(
1835 sols[0]["next"],
1836 "\"31\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1837 );
1838 }
1839
1840 #[test]
1841 fn bind_value_is_visible_to_a_following_filter_and_join() {
1842 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
1846 let n = |v: i32| format!("\"{v}\"^^{xsd}");
1847 let bytes = rete_from(&[
1848 ("<http://ex/a>", "<http://ex/v>", &n(1)),
1849 ("<http://ex/b>", "<http://ex/v>", &n(2)),
1850 ("<http://ex/c>", "<http://ex/v>", &n(3)),
1851 ("<http://ex/x>", "<http://ex/v>", &n(3)),
1853 ]);
1854 let rete = Rete::open(&bytes).unwrap();
1855
1856 let q1 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
1858 ?s ex:v ?o . BIND(?o + 1 AS ?z) FILTER(?z = 3) }";
1859 let (_, s1) = eval_sparql(&rete, q1).unwrap();
1860 assert_eq!(s1.len(), 1, "only b (2+1=3) passes");
1861 assert_eq!(s1[0]["s"], "<http://ex/b>");
1862
1863 let q2 = "PREFIX ex: <http://ex/> SELECT ?s ?s2 WHERE { \
1865 ?s ex:v ?o . BIND(?o + 1 AS ?z) ?s2 ex:v ?z }";
1866 let (_, s2) = eval_sparql(&rete, q2).unwrap();
1867 let mut pairs: Vec<(String, String)> = s2
1869 .iter()
1870 .map(|b| (b["s"].clone(), b["s2"].clone()))
1871 .collect();
1872 pairs.sort();
1873 assert_eq!(
1874 pairs,
1875 vec![
1876 ("<http://ex/a>".to_string(), "<http://ex/b>".to_string()),
1877 ("<http://ex/b>".to_string(), "<http://ex/c>".to_string()),
1878 ("<http://ex/b>".to_string(), "<http://ex/x>".to_string()),
1879 ]
1880 );
1881 }
1882
1883 #[test]
1884 fn order_by_numeric_desc_then_limit() {
1885 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
1886 let bytes = rete_from(&[
1887 (
1888 "<http://ex/a>",
1889 "<http://ex/age>",
1890 &format!("\"30\"^^{xsd}"),
1891 ),
1892 (
1893 "<http://ex/b>",
1894 "<http://ex/age>",
1895 &format!("\"25\"^^{xsd}"),
1896 ),
1897 (
1898 "<http://ex/c>",
1899 "<http://ex/age>",
1900 &format!("\"40\"^^{xsd}"),
1901 ),
1902 ]);
1903 let rete = Rete::open(&bytes).unwrap();
1904 let q = "PREFIX ex: <http://ex/> \
1906 SELECT ?p WHERE { ?p ex:age ?age } ORDER BY DESC(?age) LIMIT 2";
1907 let (_, sols) = eval_sparql(&rete, q).unwrap();
1908 let ps: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
1909 assert_eq!(ps, vec!["<http://ex/c>", "<http://ex/a>"]); }
1911
1912 #[test]
1913 fn limit_early_out_two_hop_join() {
1914 let bytes = rete_from(&[
1918 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
1919 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
1920 ("<http://ex/B>", "<http://ex/k>", "<http://ex/D>"),
1921 ("<http://ex/C>", "<http://ex/k>", "<http://ex/E>"),
1922 ]);
1923 let rete = Rete::open(&bytes).unwrap();
1924 let q = "PREFIX ex: <http://ex/> SELECT ?x ?z WHERE { ?x ex:k ?y . ?y ex:k ?z }";
1925 let (_, full) = eval_sparql(&rete, q).unwrap();
1926 assert_eq!(full.len(), 3);
1927
1928 let (_, one) = eval_sparql(&rete, &format!("{q} LIMIT 1")).unwrap();
1929 assert_eq!(one.len(), 1);
1930 assert!(one.iter().all(|r| full.contains(r)));
1932
1933 let (_, all) = eval_sparql(&rete, &format!("{q} LIMIT 100")).unwrap();
1935 assert_eq!(all.len(), 3);
1936 let (_, off) = eval_sparql(&rete, &format!("{q} LIMIT 100 OFFSET 2")).unwrap();
1937 assert_eq!(off.len(), 1);
1938 }
1939
1940 #[test]
1941 fn limit_early_out_filter_over_bgp() {
1942 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
1945 let bytes = rete_from(&[
1946 ("<http://ex/a>", "<http://ex/n>", &format!("\"10\"^^{xsd}")),
1947 ("<http://ex/b>", "<http://ex/n>", &format!("\"20\"^^{xsd}")),
1948 ("<http://ex/c>", "<http://ex/n>", &format!("\"30\"^^{xsd}")),
1949 ("<http://ex/d>", "<http://ex/n>", &format!("\"40\"^^{xsd}")),
1950 ]);
1951 let rete = Rete::open(&bytes).unwrap();
1952 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { ?p ex:n ?v FILTER(?v > 15) }";
1953 let (_, full) = eval_sparql(&rete, q).unwrap();
1954 assert_eq!(full.len(), 3); let (_, two) = eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap();
1956 assert_eq!(two.len(), 2);
1957 assert!(two.iter().all(|r| full.contains(r)));
1958 }
1959
1960 #[test]
1961 fn distinct_bgp_fast_matches_general() {
1962 let bytes = rete_from(&[
1965 ("<http://ex/a>", "<http://ex/p>", "<http://ex/x>"),
1966 ("<http://ex/b>", "<http://ex/p>", "<http://ex/x>"),
1967 ("<http://ex/b>", "<http://ex/p>", "<http://ex/y>"),
1968 ("<http://ex/c>", "<http://ex/p>", "<http://ex/z>"),
1969 ]);
1970 let rete = Rete::open(&bytes).unwrap();
1971 let q = "PREFIX ex: <http://ex/> SELECT DISTINCT ?o WHERE { ?s ex:p ?o }";
1973 let (proj, sols) = eval_sparql(&rete, q).unwrap();
1974 assert_eq!(proj, vec!["o"]);
1975 let mut os: Vec<&str> = sols.iter().map(|b| b["o"].as_str()).collect();
1976 os.sort();
1977 assert_eq!(os, vec!["<http://ex/x>", "<http://ex/y>", "<http://ex/z>"]);
1978 assert_eq!(
1980 eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap().1.len(),
1981 2
1982 );
1983 let q2 = "PREFIX ex: <http://ex/> SELECT DISTINCT ?s ?o WHERE { ?s ex:p ?o }";
1985 assert_eq!(eval_sparql(&rete, q2).unwrap().1.len(), 4);
1986 }
1987
1988 #[test]
1989 fn limit_caps_solutions() {
1990 let bytes = rete_from(&[
1991 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
1992 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
1993 ("<http://ex/c>", "<http://ex/p>", "<http://ex/3>"),
1994 ]);
1995 let rete = Rete::open(&bytes).unwrap();
1996 let (_, sols) =
1997 eval_sparql(&rete, "SELECT ?x WHERE { ?x <http://ex/p> ?y } LIMIT 2").unwrap();
1998 assert_eq!(sols.len(), 2);
1999 }
2000}