1use spargebra::Query;
13
14use crate::bgp::{Binding, PatternTerm, TriplePattern};
15use crate::file::Rete;
16
17mod aggregate;
18mod diag;
19mod eval;
20mod expr;
21mod lower;
22mod path;
23mod ql;
24
25pub use diag::{warnings_json, QueryWarning, WarningSeverity};
26use eval::{ask_solution, instantiate, raw_solutions, run_select, run_select_communities};
27use lower::{lower_pattern, lower_select, parse_query};
28pub use lower::{parse_select, query_predicates};
29pub(crate) use eval::eval_plan_in;
32
33#[derive(Debug, thiserror::Error)]
34#[non_exhaustive]
35pub enum SparqlError {
36 #[error("parse error: {0}")]
37 Parse(String),
38 #[error("unsupported query feature: {0}")]
39 Unsupported(&'static str),
40 #[error("unsupported query feature: function <{0}> is not implemented")]
43 UnsupportedFunction(String),
44 #[error("SERVICE federation: {0}")]
48 Service(String),
49}
50
51#[derive(Debug, Clone)]
53pub struct Select {
54 pub project: Vec<String>,
56 pub distinct: bool,
58 pub offset: usize,
60 pub limit: Option<usize>,
62 pub group: Option<GroupSpec>,
64 pub extends: Vec<(String, FExpr)>,
67 pub order: Vec<(FExpr, bool)>,
69 pub having: Vec<FExpr>,
71 pub from: Vec<String>,
74 pub from_named: Option<Vec<String>>,
76 pub union_default: bool,
84 pub plan: Plan,
86 pub star_counter: usize,
91}
92
93impl Default for Select {
94 fn default() -> Self {
95 Select {
96 project: Vec::new(),
97 distinct: false,
98 offset: 0,
99 limit: None,
100 group: None,
101 extends: Vec::new(),
102 order: Vec::new(),
103 having: Vec::new(),
104 from: Vec::new(),
105 from_named: None,
106 union_default: false,
107 plan: Plan::Bgp(Vec::new()),
108 star_counter: 0,
109 }
110 }
111}
112
113#[derive(Debug, Clone)]
115pub struct GroupSpec {
116 pub by: Vec<String>,
118 pub aggs: Vec<(String, Agg)>,
120 pub pre: Vec<(String, FExpr)>,
125}
126
127#[derive(Debug, Clone)]
129pub enum Agg {
130 CountStar {
132 distinct: bool,
133 },
134 Count(String, bool),
136 Sum(String),
137 Avg(String),
138 Min(String),
139 Max(String),
140 Sample(String),
142 GroupConcat(String, String, bool),
145}
146
147#[derive(Debug, Clone)]
149#[must_use]
150pub enum Plan {
151 Bgp(Vec<TriplePattern>),
153 Join(Box<Plan>, Box<Plan>),
155 Union(Box<Plan>, Box<Plan>),
157 LeftJoin(Box<Plan>, Box<Plan>, Option<FExpr>),
160 Filter(FExpr, Box<Plan>),
162 Extend(String, FExpr, Box<Plan>),
167 Path(PatternTerm, PathAst, PatternTerm),
169 Values(Vec<String>, Vec<Vec<Option<String>>>),
172 Minus(Box<Plan>, Box<Plan>),
175 Graph(GraphTarget, Box<Plan>),
177 Subquery(Box<Select>),
181 Service {
189 silent: bool,
190 endpoint: String,
192 vars: Vec<String>,
195 query: String,
197 },
198}
199
200#[derive(Debug, Clone)]
202pub enum GraphTarget {
203 Named(String),
205 Var(String),
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum Rep {
212 One,
214 OneOrMore,
216 ZeroOrMore,
218 ZeroOrOne,
220}
221
222#[derive(Debug, Clone)]
225pub enum PathAst {
226 Pred(String, bool),
228 Rep(Box<PathAst>, Rep),
230 Seq(Box<PathAst>, Box<PathAst>),
232 Alt(Box<PathAst>, Box<PathAst>),
234 NegatedSet(Vec<String>, bool),
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum Op {
243 Eq,
244 Ne,
245 Lt,
246 Le,
247 Gt,
248 Ge,
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub enum ArithOp {
254 Add,
255 Sub,
256 Mul,
257 Div,
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum Builtin {
264 Str,
265 StrLen,
266 UCase,
267 LCase,
268 Abs,
269 Ceil,
270 Floor,
271 Round,
272 Concat,
273 SubStr,
274 StrBefore,
275 StrAfter,
276 Contains,
277 StrStarts,
278 StrEnds,
279 IsIri,
280 IsBlank,
281 IsLiteral,
282 IsNumeric,
283 Datatype,
284 Lang,
285 Regex,
286 LangMatches,
287 StrDt,
288 StrLang,
289 Iri,
290 EncodeForUri,
291 Replace,
292 Md5,
293 Sha1,
294 Sha256,
295 Sha384,
296 Sha512,
297 Year,
298 Month,
299 Day,
300 Hours,
301 Minutes,
302 Seconds,
303 Timezone,
304 Tz,
305 CastInteger,
306 CastDecimal,
307 CastFloat,
308 CastDouble,
309 CastBoolean,
310 CastString,
311 Rand,
312 Uuid,
313 StrUuid,
314 BNode,
315 GeoSfContains,
318 GeoSfWithin,
319 GeoSfIntersects,
320 GeoSfDisjoint,
321 GeoSfEquals,
322 GeoDistance,
323 GeoEnvelope,
324 Geo3Distance,
326 Geo3Contains,
327 Geo3Within,
328 Geo3Adjacent,
329 TripleTerm,
332 IsTriple,
334 Subject,
336 Predicate,
337 Object,
338}
339
340#[derive(Debug, Clone)]
342pub enum FExpr {
343 Var(String),
344 Const(String),
346 Arith(ArithOp, Box<FExpr>, Box<FExpr>),
348 Func(Builtin, Vec<FExpr>),
350 Coalesce(Vec<FExpr>),
352 If(Box<FExpr>, Box<FExpr>, Box<FExpr>),
354 In(Box<FExpr>, Vec<FExpr>),
356 SameTerm(Box<FExpr>, Box<FExpr>),
358 Compare(Op, Box<FExpr>, Box<FExpr>),
359 And(Box<FExpr>, Box<FExpr>),
360 Or(Box<FExpr>, Box<FExpr>),
361 Not(Box<FExpr>),
362 Bound(String),
363 Exists(Box<Plan>),
366}
367
368type ExistsCache = std::collections::HashMap<*const Plan, ExistsEntry>;
371
372struct ExistsEntry {
376 sols: Vec<crate::row::Row>,
377 probe: Option<ExistsProbe>,
378}
379
380struct ExistsProbe {
384 svars: Vec<usize>,
386 jvars: Vec<usize>,
388 keys: std::collections::HashSet<Vec<crate::row::Val>>,
390 partial: Vec<crate::row::Row>,
392}
393
394fn build_exists_probe(b: &crate::row::Row, sols: &[crate::row::Row]) -> ExistsProbe {
397 let mask = crate::row::bound_mask(sols, b.len());
398 let svars: Vec<usize> = (0..b.len()).filter(|&i| mask[i]).collect();
399 let jvars: Vec<usize> = svars.iter().copied().filter(|&i| b[i].is_some()).collect();
400 let mut keys = std::collections::HashSet::new();
401 let mut partial = Vec::new();
402 for s in sols {
403 match jvars
404 .iter()
405 .map(|&i| s[i].clone())
406 .collect::<Option<Vec<crate::row::Val>>>()
407 {
408 Some(k) => {
409 keys.insert(k);
410 }
411 None => partial.push(s.clone()),
412 }
413 }
414 ExistsProbe {
415 svars,
416 jvars,
417 keys,
418 partial,
419 }
420}
421
422fn exists_matches(b: &crate::row::Row, entry: &ExistsEntry) -> bool {
426 let probe = entry.probe.as_ref().unwrap();
427 let bj: Vec<usize> = probe
428 .svars
429 .iter()
430 .copied()
431 .filter(|&i| b[i].is_some())
432 .collect();
433 if bj == probe.jvars {
434 let k: Vec<crate::row::Val> = probe.jvars.iter().map(|&i| b[i].clone().unwrap()).collect();
435 probe.keys.contains(&k)
436 || probe
437 .partial
438 .iter()
439 .any(|s| crate::row::compatible_rows(b, s))
440 } else {
441 entry.sols.iter().any(|s| crate::row::compatible_rows(b, s))
442 }
443}
444
445fn lexical(token: &str) -> String {
452 crate::terms::literal_lexical(token).unwrap_or_else(|| token.to_string())
453}
454
455pub(crate) fn term_number(s: &str) -> Option<f64> {
459 as_number(s)
460}
461
462use crate::terms::as_number;
463
464fn compare(op: Op, a: &str, b: &str) -> bool {
466 use std::cmp::Ordering;
467 let ord = match (as_number(a), as_number(b)) {
468 (Some(x), Some(y)) => match x.partial_cmp(&y) {
469 Some(o) => o,
470 None => return false, },
472 _ => a.cmp(b),
473 };
474 match op {
475 Op::Eq => ord == Ordering::Equal,
476 Op::Ne => ord != Ordering::Equal,
477 Op::Lt => ord == Ordering::Less,
478 Op::Le => ord != Ordering::Greater,
479 Op::Gt => ord == Ordering::Greater,
480 Op::Ge => ord != Ordering::Less,
481 }
482}
483
484#[derive(Debug, Clone)]
486#[must_use]
487#[non_exhaustive]
488pub enum QueryOutput {
489 Select(Vec<String>, Vec<Binding>),
491 Ask(bool),
493 Construct(Vec<(String, String, String)>),
495}
496
497#[derive(Debug, Clone, PartialEq, Eq)]
500#[must_use]
501#[non_exhaustive]
502pub enum SummaryQueryShape {
503 PredicateCount { predicate: String, variable: String },
505 TripleCount { variable: String },
507 PredicateTotals {
509 predicate_variable: String,
510 count_variable: String,
511 },
512 PredicateList { variable: String },
514 PredicateDistinctCount { variable: String },
516 TripleExists,
518 PredicateExists { predicate: String },
520}
521
522#[derive(Debug, Clone, PartialEq, Eq)]
526pub struct RoutedTriplePattern {
527 pub subject: Option<String>,
528 pub predicate: Option<String>,
529 pub object: Option<String>,
530}
531
532pub fn routed_triple_pattern(query: &str) -> Result<Option<RoutedTriplePattern>, SparqlError> {
537 let parsed = parse_query(query)?;
538 let sel = match parsed {
539 Query::Select {
540 pattern, dataset, ..
541 } => lower_select(&pattern, &dataset)?,
542 Query::Ask { pattern, .. } => lower_pattern(&pattern)?,
543 Query::Construct { pattern, .. } => lower_pattern(&pattern)?,
544 Query::Describe { pattern, .. } => lower_pattern(&pattern)?,
545 };
546 if !sel.from.is_empty() || sel.from_named.is_some() {
547 return Ok(None);
548 }
549 let Plan::Bgp(patterns) = sel.plan else {
550 return Ok(None);
551 };
552 let [tp] = patterns.as_slice() else {
553 return Ok(None);
554 };
555 Ok(Some(RoutedTriplePattern {
556 subject: term_const(&tp.s),
557 predicate: term_const(&tp.p),
558 object: term_const(&tp.o),
559 }))
560}
561
562fn term_const(term: &PatternTerm) -> Option<String> {
563 match term {
564 PatternTerm::Const(t) => Some(t.clone()),
565 PatternTerm::Var(_) => None,
566 }
567}
568
569pub fn summary_query_shape(query: &str) -> Result<Option<SummaryQueryShape>, SparqlError> {
576 let parsed = parse_query(query)?;
577 match parsed {
578 Query::Select {
579 pattern, dataset, ..
580 } => {
581 let sel = lower_select(&pattern, &dataset)?;
582 if !sel.from.is_empty()
583 || sel.from_named.is_some()
584 || sel.offset != 0
585 || sel.limit.is_some()
586 || !sel.order.is_empty()
587 || !sel.having.is_empty()
588 {
589 return Ok(None);
590 }
591 if sel.distinct {
592 if sel.group.is_some() || !sel.extends.is_empty() {
593 return Ok(None);
594 }
595 let [projected] = sel.project.as_slice() else {
596 return Ok(None);
597 };
598 let Some(SummaryPatternShape::AnyPredicate { variable }) =
599 single_summary_pattern(&sel.plan)
600 else {
601 return Ok(None);
602 };
603 return if projected == &variable {
604 Ok(Some(SummaryQueryShape::PredicateList { variable }))
605 } else {
606 Ok(None)
607 };
608 }
609 let Some(group) = &sel.group else {
610 return Ok(None);
611 };
612 if group.aggs.len() != 1 {
613 return Ok(None);
614 }
615 match group.by.as_slice() {
616 [] => match &group.aggs[0].1 {
617 Agg::CountStar { distinct: false } => {
618 let Some(variable) = public_aggregate_variable(&sel, &group.aggs[0].0)
619 else {
620 return Ok(None);
621 };
622 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
623 SummaryPatternShape::Predicate(predicate) => {
624 SummaryQueryShape::PredicateCount {
625 predicate,
626 variable,
627 }
628 }
629 SummaryPatternShape::AnyPredicate { .. } => {
630 SummaryQueryShape::TripleCount { variable }
631 }
632 }))
633 }
634 Agg::Count(counted, true) => {
635 let Some(public_variable) =
636 public_aggregate_variable(&sel, &group.aggs[0].0)
637 else {
638 return Ok(None);
639 };
640 let Some(SummaryPatternShape::AnyPredicate { variable }) =
641 single_summary_pattern(&sel.plan)
642 else {
643 return Ok(None);
644 };
645 if &variable != counted {
646 return Ok(None);
647 }
648 Ok(Some(SummaryQueryShape::PredicateDistinctCount {
649 variable: public_variable,
650 }))
651 }
652 _ => Ok(None),
653 },
654 [group_var] => {
655 if !matches!(group.aggs[0].1, Agg::CountStar { distinct: false }) {
656 return Ok(None);
657 }
658 let Some(SummaryPatternShape::AnyPredicate { variable }) =
659 single_summary_pattern(&sel.plan)
660 else {
661 return Ok(None);
662 };
663 if &variable != group_var {
664 return Ok(None);
665 }
666 let Some(count_variable) =
667 public_group_aggregate_variable(&sel, &group.aggs[0].0, group_var)
668 else {
669 return Ok(None);
670 };
671 Ok(Some(SummaryQueryShape::PredicateTotals {
672 predicate_variable: group_var.clone(),
673 count_variable,
674 }))
675 }
676 _ => Ok(None),
677 }
678 }
679 Query::Ask { pattern, .. } => {
680 let sel = lower_pattern(&pattern)?;
681 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
682 SummaryPatternShape::Predicate(predicate) => {
683 SummaryQueryShape::PredicateExists { predicate }
684 }
685 SummaryPatternShape::AnyPredicate { .. } => SummaryQueryShape::TripleExists,
686 }))
687 }
688 Query::Construct { .. } | Query::Describe { .. } => Ok(None),
689 }
690}
691
692enum SummaryPatternShape {
693 Predicate(String),
694 AnyPredicate { variable: String },
695}
696
697fn single_summary_pattern(plan: &Plan) -> Option<SummaryPatternShape> {
698 let Plan::Bgp(patterns) = plan else {
699 return None;
700 };
701 let [tp] = patterns.as_slice() else {
702 return None;
703 };
704 let (PatternTerm::Var(s), PatternTerm::Var(o)) = (&tp.s, &tp.o) else {
705 return None;
706 };
707 if s == o {
708 return None;
709 }
710 match &tp.p {
711 PatternTerm::Const(p) => Some(SummaryPatternShape::Predicate(p.clone())),
712 PatternTerm::Var(p) if p != s && p != o => Some(SummaryPatternShape::AnyPredicate {
713 variable: p.clone(),
714 }),
715 _ => None,
716 }
717}
718
719fn public_aggregate_variable(sel: &Select, aggregate_var: &str) -> Option<String> {
720 let [projected] = sel.project.as_slice() else {
721 return None;
722 };
723 if projected == aggregate_var {
724 return Some(projected.clone());
725 }
726 sel.extends.iter().find_map(|(var, expr)| match expr {
727 FExpr::Var(source) if var == projected && source == aggregate_var => Some(var.clone()),
728 _ => None,
729 })
730}
731
732fn public_group_aggregate_variable(
733 sel: &Select,
734 aggregate_var: &str,
735 group_var: &str,
736) -> Option<String> {
737 let [projected_group, projected_aggregate] = sel.project.as_slice() else {
738 return None;
739 };
740 if projected_group != group_var {
741 return None;
742 }
743 if projected_aggregate == aggregate_var {
744 return Some(projected_aggregate.clone());
745 }
746 sel.extends.iter().find_map(|(var, expr)| match expr {
747 FExpr::Var(source) if var == projected_aggregate && source == aggregate_var => {
748 Some(var.clone())
749 }
750 _ => None,
751 })
752}
753
754#[derive(Debug, Clone, Copy)]
757pub struct CommunityPartial {
758 pub community: usize,
759 pub subjects: usize,
760 pub rows: usize,
761}
762
763pub type CommunitySelect = (Vec<String>, Vec<Binding>, Vec<CommunityPartial>);
766
767pub fn eval_select_communities(
778 rete: &Rete,
779 query: &str,
780 round: Option<usize>,
781) -> Result<CommunitySelect, SparqlError> {
782 let parsed = parse_query(query)?;
783 let out = match parsed {
784 Query::Select {
785 pattern, dataset, ..
786 } => run_select_communities(rete, &lower_select(&pattern, &dataset)?, round),
787 _ => Err(SparqlError::Unsupported(
788 "community-split evaluation supports SELECT queries only",
789 )),
790 };
791 match rete.take_service_error() {
794 Some(e) => Err(SparqlError::Service(e)),
795 None => out,
796 }
797}
798
799pub fn eval_query(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
801 eval_query_with(rete, query, QueryOpts::default())
802}
803
804pub fn eval_query_reasoned(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
809 eval_query_with(
810 rete,
811 query,
812 QueryOpts {
813 reason: true,
814 ..QueryOpts::default()
815 },
816 )
817}
818
819#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
823pub struct QueryOpts {
824 pub reason: bool,
826 pub union_default_graph: bool,
832}
833
834pub fn eval_query_with(
837 rete: &Rete,
838 query: &str,
839 opts: QueryOpts,
840) -> Result<QueryOutput, SparqlError> {
841 diag::begin();
845 let out = eval_query_inner(rete, query, opts);
846 match rete.take_service_error() {
850 Some(e) => Err(SparqlError::Service(e)),
851 None => out,
852 }
853}
854
855pub fn eval_query_with_warnings(
872 rete: &Rete,
873 query: &str,
874 opts: QueryOpts,
875) -> Result<(QueryOutput, Vec<QueryWarning>), SparqlError> {
876 let out = eval_query_with(rete, query, opts)?;
877 let warnings = diag::finish(&out);
878 Ok((out, warnings))
879}
880
881fn maybe_reason(rete: &Rete, mut sel: Select, reason: bool) -> Select {
884 if reason {
885 let projected = sel.project.clone();
886 sel.plan = ql::reason_rewrite(sel.plan, rete, &projected);
887 }
888 sel
889}
890
891fn apply_opts(mut sel: Select, opts: QueryOpts) -> Select {
896 sel.union_default = opts.union_default_graph;
897 sel
898}
899
900fn eval_query_inner(rete: &Rete, query: &str, opts: QueryOpts) -> Result<QueryOutput, SparqlError> {
901 let reason = opts.reason;
902 let parsed = parse_query(query)?;
903 match parsed {
904 Query::Select {
905 pattern, dataset, ..
906 } => {
907 let (vars, rows) = run_select(
908 rete,
909 &maybe_reason(
910 rete,
911 apply_opts(lower_select(&pattern, &dataset)?, opts),
912 reason,
913 ),
914 );
915 Ok(QueryOutput::Select(vars, rows))
916 }
917 Query::Ask { pattern, .. } => {
918 let sel = maybe_reason(rete, apply_opts(lower_pattern(&pattern)?, opts), reason);
919 Ok(QueryOutput::Ask(ask_solution(rete, &sel)))
920 }
921 Query::Construct {
922 template, pattern, ..
923 } => {
924 let sel = maybe_reason(rete, apply_opts(lower_pattern(&pattern)?, opts), reason);
925 let (ctx, sols) = raw_solutions(rete, &sel);
926 Ok(QueryOutput::Construct(instantiate(&ctx, &template, &sols)))
927 }
928 Query::Describe {
929 pattern, dataset, ..
930 } => {
931 let sel = maybe_reason(
934 rete,
935 apply_opts(lower_select(&pattern, &dataset)?, opts),
936 reason,
937 );
938 let (ctx, rows) = raw_solutions(rete, &sel);
939 let mut resources = std::collections::BTreeSet::new();
940 for row in &rows {
941 if sel.project.is_empty() {
942 for val in row.iter().flatten() {
943 if let Some(t) = ctx.resolver.str_of(val) {
944 resources.insert(t.to_string());
945 }
946 }
947 } else {
948 for v in &sel.project {
949 if let Some(val) = ctx.slots.slot(v).and_then(|s| row[s].as_ref()) {
950 if let Some(t) = ctx.resolver.str_of(val) {
951 resources.insert(t.to_string());
952 }
953 }
954 }
955 }
956 }
957 let mut triples = std::collections::BTreeSet::new();
958 for r in &resources {
959 for t in rete.query(Some(r), None, None) {
960 triples.insert(t);
961 }
962 }
963 Ok(QueryOutput::Construct(triples.into_iter().collect()))
964 }
965 }
966}
967
968pub fn eval_sparql(rete: &Rete, query: &str) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
972 eval_sparql_opts(rete, query, false)
973}
974
975pub fn eval_sparql_reasoned(
977 rete: &Rete,
978 query: &str,
979) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
980 eval_sparql_opts(rete, query, true)
981}
982
983fn eval_sparql_opts(
984 rete: &Rete,
985 query: &str,
986 reason: bool,
987) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
988 let sel = maybe_reason(rete, parse_select(query)?, reason);
989 let out = run_select(rete, &sel);
990 match rete.take_service_error() {
992 Some(e) => Err(SparqlError::Service(e)),
993 None => Ok(out),
994 }
995}
996
997pub(crate) fn fmt_num_typed(x: f64) -> String {
1003 if x.fract() == 0.0 {
1004 format!(
1005 "\"{}\"^^<http://www.w3.org/2001/XMLSchema#integer>",
1006 x as i64
1007 )
1008 } else {
1009 let cleaned: f64 = format!("{x:.14e}").parse().unwrap_or(x);
1013 format!("\"{cleaned}\"^^<http://www.w3.org/2001/XMLSchema#decimal>")
1014 }
1015}
1016
1017fn reverse(ast: PathAst) -> PathAst {
1019 match ast {
1020 PathAst::Pred(p, r) => PathAst::Pred(p, !r),
1021 PathAst::Rep(inner, rep) => PathAst::Rep(Box::new(reverse(*inner)), rep),
1022 PathAst::Seq(a, b) => PathAst::Seq(Box::new(reverse(*b)), Box::new(reverse(*a))),
1023 PathAst::Alt(a, b) => PathAst::Alt(Box::new(reverse(*a)), Box::new(reverse(*b))),
1024 PathAst::NegatedSet(s, r) => PathAst::NegatedSet(s, !r),
1025 }
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030 use super::*;
1031 use crate::dictionary::DictionaryBuilder;
1032 use crate::index::GraphIndexBuilder;
1033 use crate::write_file;
1034
1035 fn rete_from(triples: &[(&str, &str, &str)]) -> Vec<u8> {
1036 let mut db = DictionaryBuilder::new();
1037 for (s, p, o) in triples {
1038 db.observe(s, p, o);
1039 }
1040 let dict = db.build();
1041 let mut ib = GraphIndexBuilder::new();
1042 for (s, p, o) in triples {
1043 ib.push(dict.encode(s, p, o).unwrap());
1044 }
1045 write_file(&dict, &ib.build(), false, &[], 0)
1046 }
1047
1048 #[test]
1049 fn parses_select_with_two_patterns() {
1050 let q = r#"
1051 PREFIX ex: <http://ex/>
1052 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }
1053 "#;
1054 let sel = parse_select(q).unwrap();
1055 assert_eq!(sel.project, vec!["x", "z"]);
1056 match &sel.plan {
1057 Plan::Bgp(p) => assert_eq!(p.len(), 2),
1058 other => panic!("expected a BGP plan, got {other:?}"),
1059 }
1060 }
1061
1062 #[test]
1063 fn evaluates_two_hop_select() {
1064 let bytes = rete_from(&[
1065 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1066 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1067 ]);
1068 let rete = Rete::open(&bytes).unwrap();
1069 let q = r#"PREFIX ex: <http://ex/>
1070 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }"#;
1071 let (proj, sols) = eval_sparql(&rete, q).unwrap();
1072 assert_eq!(proj, vec!["x", "z"]);
1073 assert_eq!(sols.len(), 1);
1074 assert_eq!(sols[0]["x"], "<http://ex/Alice>");
1075 assert_eq!(sols[0]["z"], "<http://ex/Carol>");
1076 }
1077
1078 #[test]
1079 fn union_returns_both_sides() {
1080 let bytes = rete_from(&[
1081 ("<http://ex/Alice>", "<http://ex/likes>", "<http://ex/Tea>"),
1082 ("<http://ex/Bob>", "<http://ex/hates>", "<http://ex/Tea>"),
1083 ]);
1084 let rete = Rete::open(&bytes).unwrap();
1085 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { \
1086 { ?p ex:likes ex:Tea } UNION { ?p ex:hates ex:Tea } }";
1087 let (_, sols) = eval_sparql(&rete, q).unwrap();
1088 let mut who: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
1089 who.sort();
1090 assert_eq!(who, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1091 }
1092
1093 #[test]
1094 fn optional_keeps_left_when_right_absent() {
1095 let bytes = rete_from(&[
1097 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1098 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1099 ("<http://ex/Alice>", "<http://ex/email>", "\"a@ex\""),
1100 ]);
1101 let rete = Rete::open(&bytes).unwrap();
1102 let q = "PREFIX ex: <http://ex/> SELECT ?p ?e WHERE { \
1103 ?p ex:name ?n . OPTIONAL { ?p ex:email ?e } }";
1104 let (_, sols) = eval_sparql(&rete, q).unwrap();
1105 assert_eq!(sols.len(), 2, "both people present");
1106 let alice = sols.iter().find(|b| b["p"] == "<http://ex/Alice>").unwrap();
1107 assert_eq!(alice["e"], "\"a@ex\"");
1108 let bob = sols.iter().find(|b| b["p"] == "<http://ex/Bob>").unwrap();
1109 assert!(!bob.contains_key("e"), "Bob has no email binding");
1110 }
1111
1112 #[test]
1113 fn numeric_filter_on_typed_literal() {
1114 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
1116 let bytes = rete_from(&[
1117 (
1118 "<http://ex/Alice>",
1119 "<http://ex/age>",
1120 &format!("\"30\"^^{xsd}"),
1121 ),
1122 (
1123 "<http://ex/Bob>",
1124 "<http://ex/age>",
1125 &format!("\"25\"^^{xsd}"),
1126 ),
1127 ]);
1128 let rete = Rete::open(&bytes).unwrap();
1129 let q = "PREFIX ex: <http://ex/> \
1130 SELECT ?p WHERE { ?p ex:age ?age . FILTER(?age > 27) }";
1131 let (_, sols) = eval_sparql(&rete, q).unwrap();
1132 assert_eq!(sols.len(), 1);
1133 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1134 }
1135
1136 #[test]
1137 fn filter_equality_and_boolean_logic() {
1138 let bytes = rete_from(&[
1139 ("<http://ex/Alice>", "<http://ex/city>", "<http://ex/NYC>"),
1140 ("<http://ex/Bob>", "<http://ex/city>", "<http://ex/LA>"),
1141 ("<http://ex/Carol>", "<http://ex/city>", "<http://ex/NYC>"),
1142 ]);
1143 let rete = Rete::open(&bytes).unwrap();
1144 let q = "PREFIX ex: <http://ex/> \
1145 SELECT ?p WHERE { ?p ex:city ?c . FILTER(?c = ex:NYC && ?p != ex:Carol) }";
1146 let (_, sols) = eval_sparql(&rete, q).unwrap();
1147 assert_eq!(sols.len(), 1);
1148 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1149 }
1150
1151 #[test]
1152 fn distinct_collapses_duplicate_projections() {
1153 let bytes = rete_from(&[
1155 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1156 (
1157 "<http://ex/Alice>",
1158 "<http://ex/knows>",
1159 "<http://ex/Carol>",
1160 ),
1161 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1162 ("<http://ex/Carol>", "<http://ex/knows>", "<http://ex/Dave>"),
1163 ]);
1164 let rete = Rete::open(&bytes).unwrap();
1165 let base = "PREFIX ex: <http://ex/> SELECT {} ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1166
1167 let (_, non_distinct) = eval_sparql(&rete, &base.replace("{}", "")).unwrap();
1168 assert_eq!(non_distinct.len(), 2); let (proj, distinct) = eval_sparql(&rete, &base.replace("{}", "DISTINCT")).unwrap();
1171 assert_eq!(proj, vec!["z"]);
1172 assert_eq!(distinct.len(), 1);
1173 assert_eq!(distinct[0]["z"], "<http://ex/Dave>");
1174 assert!(!distinct[0].contains_key("x"));
1176 }
1177
1178 #[test]
1179 fn transitive_path_reachability() {
1180 let bytes = rete_from(&[
1182 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
1183 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
1184 ("<http://ex/C>", "<http://ex/k>", "<http://ex/D>"),
1185 ]);
1186 let rete = Rete::open(&bytes).unwrap();
1187
1188 let q = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k+ ?y }";
1190 let (_, sols) = eval_sparql(&rete, q).unwrap();
1191 let mut ys: Vec<&str> = sols.iter().map(|b| b["y"].as_str()).collect();
1192 ys.sort();
1193 assert_eq!(ys, vec!["<http://ex/B>", "<http://ex/C>", "<http://ex/D>"]);
1194
1195 let q0 = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k* ?y }";
1197 let (_, s0) = eval_sparql(&rete, q0).unwrap();
1198 assert!(s0.iter().any(|b| b["y"] == "<http://ex/A>"));
1199 assert_eq!(s0.len(), 4); }
1201
1202 #[test]
1203 fn sequence_and_alternative_paths() {
1204 let bytes = rete_from(&[
1206 ("<http://ex/Alice>", "<http://ex/parent>", "<http://ex/Bob>"),
1207 ("<http://ex/Bob>", "<http://ex/parent>", "<http://ex/Carol>"),
1208 (
1209 "<http://ex/Alice>",
1210 "<http://ex/stepparent>",
1211 "<http://ex/Dave>",
1212 ),
1213 ]);
1214 let rete = Rete::open(&bytes).unwrap();
1215
1216 let q = "PREFIX ex: <http://ex/> SELECT ?g WHERE { ex:Alice ex:parent/ex:parent ?g }";
1218 let (_, sols) = eval_sparql(&rete, q).unwrap();
1219 assert_eq!(sols.len(), 1);
1220 assert_eq!(sols[0]["g"], "<http://ex/Carol>");
1221
1222 let qa = "PREFIX ex: <http://ex/> \
1224 SELECT ?p WHERE { ex:Alice ex:parent|ex:stepparent ?p }";
1225 let (_, sa) = eval_sparql(&rete, qa).unwrap();
1226 let mut ps: Vec<&str> = sa.iter().map(|b| b["p"].as_str()).collect();
1227 ps.sort();
1228 assert_eq!(ps, vec!["<http://ex/Bob>", "<http://ex/Dave>"]);
1229 }
1230
1231 #[test]
1232 fn group_concat_aggregate() {
1233 let bytes = rete_from(&[
1234 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1235 (
1236 "<http://ex/Alice>",
1237 "<http://ex/knows>",
1238 "<http://ex/Carol>",
1239 ),
1240 ]);
1241 let rete = Rete::open(&bytes).unwrap();
1242 let q = "PREFIX ex: <http://ex/> \
1243 SELECT (GROUP_CONCAT(?f; SEPARATOR=\"|\") AS ?fs) WHERE { ex:Alice ex:knows ?f }";
1244 let (_, sols) = eval_sparql(&rete, q).unwrap();
1245 assert_eq!(sols.len(), 1);
1246 let fs = sols[0]["fs"].trim_matches('"');
1248 let mut parts: Vec<&str> = fs.split('|').collect();
1249 parts.sort();
1250 assert_eq!(parts, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1251 }
1252
1253 #[test]
1254 fn group_by_having() {
1255 let bytes = rete_from(&[
1257 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1258 (
1259 "<http://ex/Alice>",
1260 "<http://ex/knows>",
1261 "<http://ex/Carol>",
1262 ),
1263 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1264 ]);
1265 let rete = Rete::open(&bytes).unwrap();
1266 let q = "PREFIX ex: <http://ex/> SELECT ?p (COUNT(?f) AS ?n) \
1268 WHERE { ?p ex:knows ?f } GROUP BY ?p HAVING (COUNT(?f) > 1)";
1269 let (_, sols) = eval_sparql(&rete, q).unwrap();
1270 assert_eq!(sols.len(), 1);
1271 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1272 assert_eq!(
1273 sols[0]["n"],
1274 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1275 );
1276 }
1277
1278 #[test]
1279 fn count_group_by() {
1280 let bytes = rete_from(&[
1282 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1283 (
1284 "<http://ex/Alice>",
1285 "<http://ex/knows>",
1286 "<http://ex/Carol>",
1287 ),
1288 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1289 ]);
1290 let rete = Rete::open(&bytes).unwrap();
1291 let q = "PREFIX ex: <http://ex/> \
1292 SELECT ?p (COUNT(?f) AS ?n) WHERE { ?p ex:knows ?f } GROUP BY ?p";
1293 let (_, sols) = eval_sparql(&rete, q).unwrap();
1294 let mut counts: Vec<(String, String)> = sols
1295 .iter()
1296 .map(|b| (b["p"].clone(), b["n"].clone()))
1297 .collect();
1298 counts.sort();
1299 assert_eq!(
1300 counts,
1301 vec![
1302 (
1303 "<http://ex/Alice>".into(),
1304 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1305 ),
1306 (
1307 "<http://ex/Bob>".into(),
1308 "\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1309 ),
1310 ]
1311 );
1312 }
1313
1314 #[test]
1315 fn global_count_star() {
1316 let bytes = rete_from(&[
1317 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
1318 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
1319 ]);
1320 let rete = Rete::open(&bytes).unwrap();
1321 let (_, sols) = eval_sparql(&rete, "SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1322 assert_eq!(sols.len(), 1);
1323 assert_eq!(
1324 sols[0]["n"],
1325 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1326 );
1327 }
1328
1329 #[test]
1330 fn summary_query_shape_classifies_only_exact_predicate_totals() {
1331 let count = summary_query_shape(
1332 "PREFIX ex: <http://ex/> SELECT (COUNT(*) AS ?n) WHERE { ?s ex:p ?o }",
1333 )
1334 .unwrap();
1335 assert_eq!(
1336 count,
1337 Some(SummaryQueryShape::PredicateCount {
1338 predicate: "<http://ex/p>".into(),
1339 variable: "n".into(),
1340 })
1341 );
1342
1343 let total = summary_query_shape("SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1344 assert_eq!(
1345 total,
1346 Some(SummaryQueryShape::TripleCount {
1347 variable: "n".into(),
1348 })
1349 );
1350
1351 let by_pred =
1352 summary_query_shape("SELECT ?p (COUNT(*) AS ?n) WHERE { ?s ?p ?o } GROUP BY ?p")
1353 .unwrap();
1354 assert_eq!(
1355 by_pred,
1356 Some(SummaryQueryShape::PredicateTotals {
1357 predicate_variable: "p".into(),
1358 count_variable: "n".into(),
1359 })
1360 );
1361
1362 let predicates = summary_query_shape("SELECT DISTINCT ?p WHERE { ?s ?p ?o }").unwrap();
1363 assert_eq!(
1364 predicates,
1365 Some(SummaryQueryShape::PredicateList {
1366 variable: "p".into(),
1367 })
1368 );
1369
1370 let predicate_count =
1371 summary_query_shape("SELECT (COUNT(DISTINCT ?p) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1372 assert_eq!(
1373 predicate_count,
1374 Some(SummaryQueryShape::PredicateDistinctCount {
1375 variable: "n".into(),
1376 })
1377 );
1378
1379 let ask = summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o }").unwrap();
1380 assert_eq!(
1381 ask,
1382 Some(SummaryQueryShape::PredicateExists {
1383 predicate: "<http://ex/p>".into(),
1384 })
1385 );
1386
1387 let any_ask = summary_query_shape("ASK { ?s ?p ?o }").unwrap();
1388 assert_eq!(any_ask, Some(SummaryQueryShape::TripleExists));
1389
1390 let constrained =
1391 summary_query_shape("PREFIX ex: <http://ex/> ASK { ex:a ex:p ?o }").unwrap();
1392 assert_eq!(constrained, None);
1393
1394 let filtered =
1395 summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o FILTER(?s = ?o) }")
1396 .unwrap();
1397 assert_eq!(filtered, None);
1398 }
1399
1400 #[test]
1401 fn filter_exists_and_not_exists() {
1402 let bytes = rete_from(&[
1404 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1405 (
1406 "<http://ex/Alice>",
1407 "<http://ex/knows>",
1408 "<http://ex/Carol>",
1409 ),
1410 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1411 ]);
1412 let rete = Rete::open(&bytes).unwrap();
1413
1414 let q_exists = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1416 ex:Alice ex:knows ?f . FILTER EXISTS { ?f ex:knows ?x } }";
1417 let (_, e) = eval_sparql(&rete, q_exists).unwrap();
1418 assert_eq!(
1419 e.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1420 vec!["<http://ex/Bob>"]
1421 );
1422
1423 let q_not = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1425 ex:Alice ex:knows ?f . FILTER NOT EXISTS { ?f ex:knows ?x } }";
1426 let (_, n) = eval_sparql(&rete, q_not).unwrap();
1427 assert_eq!(
1428 n.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1429 vec!["<http://ex/Carol>"]
1430 );
1431 }
1432
1433 #[test]
1434 fn ask_repeated_variable_pattern() {
1435 let yes = rete_from(&[
1438 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1439 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Bob>"),
1440 ]);
1441 let rete = Rete::open(&yes).unwrap();
1442 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1443 QueryOutput::Ask(b) => assert!(b, "Bob knows himself"),
1444 other => panic!("expected Ask, got {other:?}"),
1445 }
1446 let no = rete_from(&[("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>")]);
1449 let rete = Rete::open(&no).unwrap();
1450 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1451 QueryOutput::Ask(b) => assert!(!b, "nobody knows themselves"),
1452 other => panic!("expected Ask, got {other:?}"),
1453 }
1454 }
1455
1456 #[test]
1457 fn minus_excludes_compatible() {
1458 let bytes = rete_from(&[
1460 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1461 (
1462 "<http://ex/Alice>",
1463 "<http://ex/knows>",
1464 "<http://ex/Carol>",
1465 ),
1466 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1467 ]);
1468 let rete = Rete::open(&bytes).unwrap();
1469 let q = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1471 ex:Alice ex:knows ?f . MINUS { ?f ex:knows ?x } }";
1472 let (_, sols) = eval_sparql(&rete, q).unwrap();
1473 let fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1474 assert_eq!(fs, vec!["<http://ex/Carol>"]);
1475 }
1476
1477 #[test]
1478 fn filter_exists_disjoint_variable() {
1479 let bytes = rete_from(&[
1484 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1485 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1486 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1487 ]);
1488 let rete = Rete::open(&bytes).unwrap();
1489 let q_none = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1490 ?x ex:a ex:Person FILTER NOT EXISTS { ?y ex:a ex:Drink } }";
1491 assert!(eval_sparql(&rete, q_none).unwrap().1.is_empty());
1492 let q_all = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1493 ?x ex:a ex:Person FILTER EXISTS { ?y ex:a ex:Drink } }";
1494 assert_eq!(eval_sparql(&rete, q_all).unwrap().1.len(), 2);
1495 }
1496
1497 #[test]
1498 fn minus_disjoint_domain_keeps_all() {
1499 let bytes = rete_from(&[
1503 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1504 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1505 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1506 ]);
1507 let rete = Rete::open(&bytes).unwrap();
1508 let q = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1509 ?x ex:a ex:Person MINUS { ?y ex:a ex:Drink } }";
1510 let (_, sols) = eval_sparql(&rete, q).unwrap();
1511 let mut xs: Vec<&str> = sols.iter().map(|b| b["x"].as_str()).collect();
1512 xs.sort();
1513 assert_eq!(xs, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1514 }
1515
1516 #[test]
1517 fn values_pushdown_selects_subset() {
1518 let bytes = rete_from(&[
1521 ("<http://ex/a>", "<http://ex/d>", "<http://ex/Bio>"),
1522 ("<http://ex/b>", "<http://ex/d>", "<http://ex/Phys>"),
1523 ("<http://ex/c>", "<http://ex/d>", "<http://ex/Chem>"),
1524 ]);
1525 let rete = Rete::open(&bytes).unwrap();
1526 let q = "PREFIX ex: <http://ex/> SELECT ?p ?disc WHERE { \
1527 VALUES ?disc { ex:Bio ex:Phys } ?p ex:d ?disc }";
1528 let (_, sols) = eval_sparql(&rete, q).unwrap();
1529 let mut got: Vec<(String, String)> = sols
1530 .iter()
1531 .map(|b| (b["p"].clone(), b["disc"].clone()))
1532 .collect();
1533 got.sort();
1534 assert_eq!(
1535 got,
1536 vec![
1537 ("<http://ex/a>".into(), "<http://ex/Bio>".into()),
1538 ("<http://ex/b>".into(), "<http://ex/Phys>".into()),
1539 ]
1540 );
1541 }
1542
1543 #[test]
1544 fn values_inline_data_joins() {
1545 let bytes = rete_from(&[
1546 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1547 (
1548 "<http://ex/Alice>",
1549 "<http://ex/knows>",
1550 "<http://ex/Carol>",
1551 ),
1552 ("<http://ex/Dave>", "<http://ex/knows>", "<http://ex/Eve>"),
1553 ]);
1554 let rete = Rete::open(&bytes).unwrap();
1555 let q = "PREFIX ex: <http://ex/> \
1557 SELECT ?f WHERE { VALUES ?p { ex:Alice } ?p ex:knows ?f }";
1558 let (_, sols) = eval_sparql(&rete, q).unwrap();
1559 let mut fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1560 fs.sort();
1561 assert_eq!(fs, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1562 }
1563
1564 #[test]
1565 fn graph_queries_over_named_graphs() {
1566 use crate::write_dataset;
1567 let mut db = DictionaryBuilder::new();
1569 let edges = [
1570 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1571 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1572 ];
1573 for (s, p, o) in edges {
1574 db.observe(s, p, o);
1575 }
1576 let dict = db.build();
1577 let mut social = GraphIndexBuilder::new();
1578 social.push(dict.encode(edges[0].0, edges[0].1, edges[0].2).unwrap());
1579 let mut profile = GraphIndexBuilder::new();
1580 profile.push(dict.encode(edges[1].0, edges[1].1, edges[1].2).unwrap());
1581 let named = vec![
1582 ("<http://ex/social>".to_string(), social.build()),
1583 ("<http://ex/profile>".to_string(), profile.build()),
1584 ];
1585 let bytes = write_dataset(
1586 &dict,
1587 &GraphIndexBuilder::new().build(),
1588 &named,
1589 true,
1590 &[],
1591 0,
1592 );
1593 let rete = Rete::open(&bytes).unwrap();
1594
1595 let q = "PREFIX ex: <http://ex/> \
1597 SELECT ?f WHERE { GRAPH ex:social { ex:Alice ex:knows ?f } }";
1598 let (_, s) = eval_sparql(&rete, q).unwrap();
1599 assert_eq!(s.len(), 1);
1600 assert_eq!(s[0]["f"], "<http://ex/Bob>");
1601
1602 let q2 = "PREFIX ex: <http://ex/> \
1604 SELECT ?g WHERE { GRAPH ?g { ex:Bob ex:age ?a } }";
1605 let (_, s2) = eval_sparql(&rete, q2).unwrap();
1606 assert_eq!(s2.len(), 1);
1607 assert_eq!(s2[0]["g"], "<http://ex/profile>");
1608
1609 let q3 = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1612 GRAPH ex:profile { ?f ex:age ?a . FILTER EXISTS { ?f ex:age ?a2 } } }";
1613 assert_eq!(eval_sparql(&rete, q3).unwrap().1.len(), 1);
1614 let q4 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
1615 GRAPH ex:social { ?s ex:knows ?o . FILTER NOT EXISTS { ?s ex:age ?a } } }";
1616 assert_eq!(eval_sparql(&rete, q4).unwrap().1.len(), 1);
1618 }
1619
1620 #[test]
1621 fn from_unions_named_graphs() {
1622 use crate::write_dataset;
1623 let mut db = DictionaryBuilder::new();
1626 let t = [
1627 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1628 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1629 ];
1630 for (s, p, o) in t {
1631 db.observe(s, p, o);
1632 }
1633 let dict = db.build();
1634 let mut g1 = GraphIndexBuilder::new();
1635 g1.push(dict.encode(t[0].0, t[0].1, t[0].2).unwrap());
1636 let mut g2 = GraphIndexBuilder::new();
1637 g2.push(dict.encode(t[1].0, t[1].1, t[1].2).unwrap());
1638 let named = vec![
1639 ("<http://ex/social>".to_string(), g1.build()),
1640 ("<http://ex/profile>".to_string(), g2.build()),
1641 ];
1642 let bytes = write_dataset(
1643 &dict,
1644 &GraphIndexBuilder::new().build(),
1645 &named,
1646 true,
1647 &[],
1648 0,
1649 );
1650 let rete = Rete::open(&bytes).unwrap();
1651
1652 let q0 = "PREFIX ex: <http://ex/> SELECT ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1654 assert!(eval_sparql(&rete, q0).unwrap().1.is_empty());
1655
1656 let q = "PREFIX ex: <http://ex/> \
1658 SELECT ?z FROM ex:social FROM ex:profile \
1659 WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1660 let (_, s) = eval_sparql(&rete, q).unwrap();
1661 assert_eq!(s.len(), 1);
1662 assert_eq!(s[0]["z"], "<http://ex/Carol>");
1663
1664 let qn = "PREFIX ex: <http://ex/> SELECT ?g FROM NAMED ex:social \
1666 WHERE { GRAPH ?g { ?x ex:knows ?y } }";
1667 let (_, sn) = eval_sparql(&rete, qn).unwrap();
1668 let gs: Vec<&str> = sn.iter().map(|b| b["g"].as_str()).collect();
1669 assert_eq!(gs, vec!["<http://ex/social>"]); }
1671
1672 #[test]
1673 fn union_default_graph_opt_in() {
1674 use crate::write_dataset;
1675 let mut db = DictionaryBuilder::new();
1679 let t = [
1680 ("<http://ex/Root>", "<http://ex/knows>", "<http://ex/Alice>"),
1681 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1682 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1683 ];
1684 for (s, p, o) in t {
1685 db.observe(s, p, o);
1686 }
1687 let dict = db.build();
1688 let enc = |i: usize| dict.encode(t[i].0, t[i].1, t[i].2).unwrap();
1689 let mut def = GraphIndexBuilder::new();
1690 def.push(enc(0));
1691 let mut g1 = GraphIndexBuilder::new();
1692 g1.push(enc(1));
1693 let mut g2 = GraphIndexBuilder::new();
1694 g2.push(enc(1)); g2.push(enc(2));
1696 let named = vec![
1697 ("<http://ex/social>".to_string(), g1.build()),
1698 ("<http://ex/profile>".to_string(), g2.build()),
1699 ];
1700 let bytes = write_dataset(&dict, &def.build(), &named, true, &[], 0);
1701 let rete = Rete::open(&bytes).unwrap();
1702 let union = QueryOpts {
1703 union_default_graph: true,
1704 ..QueryOpts::default()
1705 };
1706 let rows = |out: QueryOutput| match out {
1707 QueryOutput::Select(_, rows) => rows,
1708 other => panic!("expected Select, got {other:?}"),
1709 };
1710
1711 let q = "PREFIX ex: <http://ex/> SELECT ?s ?o WHERE { ?s ex:knows ?o }";
1715 assert_eq!(rows(eval_query(&rete, q).unwrap()).len(), 1);
1716
1717 let got = rows(eval_query_with(&rete, q, union).unwrap());
1721 assert_eq!(got.len(), 3);
1722 assert!(got.iter().any(|b| b["s"] == "<http://ex/Root>"));
1723
1724 let qj = "PREFIX ex: <http://ex/> SELECT ?z WHERE { \
1726 ex:Root ex:knows ?x . ?x ex:knows ?y . ?y ex:knows ?z }";
1727 let sj = rows(eval_query_with(&rete, qj, union).unwrap());
1728 assert_eq!(sj.len(), 1);
1729 assert_eq!(sj[0]["z"], "<http://ex/Carol>");
1730
1731 let qg = "PREFIX ex: <http://ex/> SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ex:knows ?o } }";
1733 assert_eq!(rows(eval_query_with(&rete, qg, union).unwrap()).len(), 2);
1734
1735 let qf = "PREFIX ex: <http://ex/> SELECT ?s ?o FROM ex:social WHERE { ?s ex:knows ?o }";
1738 let sf = rows(eval_query_with(&rete, qf, union).unwrap());
1739 assert_eq!(sf.len(), 1);
1740 assert_eq!(sf[0]["s"], "<http://ex/Alice>");
1741
1742 let mut db2 = DictionaryBuilder::new();
1745 db2.observe(t[1].0, t[1].1, t[1].2);
1746 let dict2 = db2.build();
1747 let mut only = GraphIndexBuilder::new();
1748 only.push(dict2.encode(t[1].0, t[1].1, t[1].2).unwrap());
1749 let named2 = vec![("<http://ex/only>".to_string(), only.build())];
1750 let bytes2 = write_dataset(
1751 &dict2,
1752 &GraphIndexBuilder::new().build(),
1753 &named2,
1754 true,
1755 &[],
1756 0,
1757 );
1758 let rete2 = Rete::open(&bytes2).unwrap();
1759 assert_eq!(rows(eval_query(&rete2, q).unwrap()).len(), 0); assert_eq!(rows(eval_query_with(&rete2, q, union).unwrap()).len(), 1);
1761 }
1762
1763 #[test]
1764 fn describe_resource() {
1765 let bytes = rete_from(&[
1766 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1767 ("<http://ex/Alice>", "<http://ex/age>", "\"30\""),
1768 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1769 ]);
1770 let rete = Rete::open(&bytes).unwrap();
1771 match eval_query(&rete, "DESCRIBE <http://ex/Alice>").unwrap() {
1773 QueryOutput::Construct(t) => assert_eq!(t.len(), 2),
1774 other => panic!("expected Construct, got {other:?}"),
1775 }
1776 let q = "PREFIX ex: <http://ex/> DESCRIBE ?x WHERE { ?x ex:age ?a }";
1778 match eval_query(&rete, q).unwrap() {
1779 QueryOutput::Construct(t) => {
1780 assert_eq!(t.len(), 3);
1782 }
1783 other => panic!("expected Construct, got {other:?}"),
1784 }
1785 }
1786
1787 #[test]
1788 fn ask_and_construct() {
1789 let bytes = rete_from(&[
1790 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1791 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1792 ]);
1793 let rete = Rete::open(&bytes).unwrap();
1794
1795 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:knows ?b }").unwrap() {
1797 QueryOutput::Ask(b) => assert!(b),
1798 other => panic!("expected Ask, got {other:?}"),
1799 }
1800 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:likes ?b }").unwrap() {
1801 QueryOutput::Ask(b) => assert!(!b),
1802 other => panic!("expected Ask, got {other:?}"),
1803 }
1804
1805 let q = "PREFIX ex: <http://ex/> \
1807 CONSTRUCT { ?b ex:knownBy ?a } WHERE { ?a ex:knows ?b }";
1808 match eval_query(&rete, q).unwrap() {
1809 QueryOutput::Construct(mut triples) => {
1810 triples.sort();
1811 assert_eq!(triples.len(), 2);
1812 assert!(triples.contains(&(
1813 "<http://ex/Bob>".into(),
1814 "<http://ex/knownBy>".into(),
1815 "<http://ex/Alice>".into(),
1816 )));
1817 }
1818 other => panic!("expected Construct, got {other:?}"),
1819 }
1820 }
1821
1822 #[test]
1823 fn substr_strbefore_strafter() {
1824 let bytes = rete_from(&[("<http://ex/a>", "<http://ex/name>", "\"Alice Smith\"")]);
1825 let rete = Rete::open(&bytes).unwrap();
1826 let q = "PREFIX ex: <http://ex/> SELECT ?first ?last ?ini WHERE { \
1827 ?p ex:name ?n . \
1828 BIND(STRBEFORE(?n, \" \") AS ?first) \
1829 BIND(STRAFTER(?n, \" \") AS ?last) \
1830 BIND(SUBSTR(?n, 1, 1) AS ?ini) }";
1831 let (_, sols) = eval_sparql(&rete, q).unwrap();
1832 assert_eq!(sols[0]["first"], "\"Alice\"");
1834 assert_eq!(sols[0]["last"], "\"Smith\"");
1835 assert_eq!(sols[0]["ini"], "\"A\"");
1836 }
1837
1838 #[test]
1839 fn concat_and_coalesce() {
1840 let bytes = rete_from(&[
1842 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1843 ("<http://ex/Alice>", "<http://ex/nick>", "\"Al\""),
1844 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1845 ]);
1846 let rete = Rete::open(&bytes).unwrap();
1847 let q = "PREFIX ex: <http://ex/> SELECT ?label WHERE { \
1849 ?p ex:name ?name . OPTIONAL { ?p ex:nick ?nick } \
1850 BIND(CONCAT(\"@\", COALESCE(?nick, ?name)) AS ?label) }";
1851 let (_, sols) = eval_sparql(&rete, q).unwrap();
1852 let mut labels: Vec<&str> = sols.iter().map(|b| b["label"].as_str()).collect();
1853 labels.sort();
1854 assert_eq!(labels, vec!["\"@Al\"", "\"@Bob\""]);
1855 }
1856
1857 #[test]
1858 fn builtin_functions() {
1859 let bytes = rete_from(&[
1860 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice Smith\""),
1861 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob Jones\""),
1862 ]);
1863 let rete = Rete::open(&bytes).unwrap();
1864 let q = "PREFIX ex: <http://ex/> SELECT ?p ?len WHERE { \
1866 ?p ex:name ?n . FILTER(CONTAINS(?n, \"Smith\")) BIND(STRLEN(?n) AS ?len) }";
1867 let (_, sols) = eval_sparql(&rete, q).unwrap();
1868 assert_eq!(sols.len(), 1);
1869 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1870 assert_eq!(
1871 sols[0]["len"],
1872 "\"11\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1873 ); }
1875
1876 #[test]
1877 fn geosparql_filter_and_functions() {
1878 let wkt = "\"POLYGON((0 0,10 0,10 10,0 10,0 0))\"^^\
1879 <http://www.opengis.net/ont/geosparql#wktLiteral>";
1880 let bytes = rete_from(&[
1881 (
1882 "<http://ex/f>",
1883 "<http://www.opengis.net/ont/geosparql#hasGeometry>",
1884 "<http://ex/f/g>",
1885 ),
1886 (
1887 "<http://ex/f/g>",
1888 "<http://www.opengis.net/ont/geosparql#asWKT>",
1889 wkt,
1890 ),
1891 (
1892 "<http://ex/f>",
1893 "<http://ex/year>",
1894 "\"1815\"^^<http://www.w3.org/2001/XMLSchema#integer>",
1895 ),
1896 ]);
1897 let rete = Rete::open(&bytes).unwrap();
1898 let pre = "PREFIX geo: <http://www.opengis.net/ont/geosparql#> \
1899 PREFIX geof: <http://www.opengis.net/def/function/geosparql/> \
1900 PREFIX uom: <http://www.opengis.net/def/uom/OGC/1.0/> PREFIX ex: <http://ex/> ";
1901
1902 let (_, s) = eval_sparql(
1904 &rete,
1905 &format!(
1906 "{pre}SELECT ?f WHERE {{ ?f ex:year ?y ; \
1907 geo:hasGeometry/geo:asWKT ?w . \
1908 FILTER(?y = 1815 && geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral)) }}"
1909 ),
1910 )
1911 .unwrap();
1912 assert_eq!(s.len(), 1, "point inside the polygon in year 1815");
1913 assert_eq!(s[0]["f"], "<http://ex/f>");
1914
1915 let (_, s) = eval_sparql(
1917 &rete,
1918 &format!(
1919 "{pre}SELECT ?f WHERE {{ \
1920 ?f geo:hasGeometry/geo:asWKT ?w . \
1921 FILTER(geof:sfContains(?w, \"POINT(50 50)\"^^geo:wktLiteral)) }}"
1922 ),
1923 )
1924 .unwrap();
1925 assert!(s.is_empty());
1926
1927 let (_, s) = eval_sparql(
1929 &rete,
1930 &format!(
1931 "{pre}SELECT ?f WHERE {{ \
1932 ?f geo:hasGeometry/geo:asWKT ?w . \
1933 FILTER(geof:sfWithin(\"POINT(5 5)\"^^geo:wktLiteral, ?w)) }}"
1934 ),
1935 )
1936 .unwrap();
1937 assert_eq!(s.len(), 1);
1938
1939 let (_, s) = eval_sparql(
1941 &rete,
1942 &format!(
1943 "{pre}SELECT ?f WHERE {{ \
1944 ?f geo:hasGeometry/geo:asWKT ?w . \
1945 FILTER(geof:sfContains(?w, \"garbage\"^^geo:wktLiteral)) }}"
1946 ),
1947 )
1948 .unwrap();
1949 assert!(s.is_empty());
1950
1951 let (_, s) = eval_sparql(
1953 &rete,
1954 &format!(
1955 "{pre}SELECT ?hit WHERE {{ \
1956 ?f geo:hasGeometry/geo:asWKT ?w . \
1957 BIND(geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral) AS ?hit) }}"
1958 ),
1959 )
1960 .unwrap();
1961 assert_eq!(
1962 s[0]["hit"],
1963 "\"true\"^^<http://www.w3.org/2001/XMLSchema#boolean>"
1964 );
1965
1966 let (_, s) = eval_sparql(
1968 &rete,
1969 &format!(
1970 "{pre}SELECT ?d WHERE {{ BIND(geof:distance(\
1971 \"POINT(0 0)\"^^geo:wktLiteral, \"POINT(0 1)\"^^geo:wktLiteral, uom:metre) AS ?d) }}"
1972 ),
1973 )
1974 .unwrap();
1975 assert!(
1976 s[0]["d"].ends_with("XMLSchema#double>"),
1977 "distance is xsd:double: {}",
1978 s[0]["d"]
1979 );
1980 let (_, s) = eval_sparql(
1981 &rete,
1982 &format!(
1983 "{pre}SELECT ?e WHERE {{ \
1984 ?f geo:hasGeometry/geo:asWKT ?w . BIND(geof:envelope(?w) AS ?e) }}"
1985 ),
1986 )
1987 .unwrap();
1988 assert!(s[0]["e"].contains("wktLiteral") && s[0]["e"].contains("POLYGON"));
1989
1990 assert!(eval_sparql(
1992 &rete,
1993 &format!(
1994 "{pre}SELECT ?x WHERE {{ \
1995 BIND(geof:buffer(\"POINT(0 0)\"^^geo:wktLiteral, 1) AS ?x) }}"
1996 )
1997 )
1998 .is_err());
1999 }
2000
2001 #[test]
2002 fn bind_arithmetic() {
2003 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2004 let bytes = rete_from(&[(
2005 "<http://ex/a>",
2006 "<http://ex/age>",
2007 &format!("\"30\"^^{xsd}"),
2008 )]);
2009 let rete = Rete::open(&bytes).unwrap();
2010 let q = "PREFIX ex: <http://ex/> \
2012 SELECT ?next WHERE { ?p ex:age ?age . BIND(?age + 1 AS ?next) FILTER(?age * 2 > 50) }";
2013 let (_, sols) = eval_sparql(&rete, q).unwrap();
2014 assert_eq!(sols.len(), 1);
2015 assert_eq!(
2016 sols[0]["next"],
2017 "\"31\"^^<http://www.w3.org/2001/XMLSchema#integer>"
2018 );
2019 }
2020
2021 #[test]
2022 fn bind_value_is_visible_to_a_following_filter_and_join() {
2023 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2027 let n = |v: i32| format!("\"{v}\"^^{xsd}");
2028 let bytes = rete_from(&[
2029 ("<http://ex/a>", "<http://ex/v>", &n(1)),
2030 ("<http://ex/b>", "<http://ex/v>", &n(2)),
2031 ("<http://ex/c>", "<http://ex/v>", &n(3)),
2032 ("<http://ex/x>", "<http://ex/v>", &n(3)),
2034 ]);
2035 let rete = Rete::open(&bytes).unwrap();
2036
2037 let q1 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
2039 ?s ex:v ?o . BIND(?o + 1 AS ?z) FILTER(?z = 3) }";
2040 let (_, s1) = eval_sparql(&rete, q1).unwrap();
2041 assert_eq!(s1.len(), 1, "only b (2+1=3) passes");
2042 assert_eq!(s1[0]["s"], "<http://ex/b>");
2043
2044 let q2 = "PREFIX ex: <http://ex/> SELECT ?s ?s2 WHERE { \
2046 ?s ex:v ?o . BIND(?o + 1 AS ?z) ?s2 ex:v ?z }";
2047 let (_, s2) = eval_sparql(&rete, q2).unwrap();
2048 let mut pairs: Vec<(String, String)> = s2
2050 .iter()
2051 .map(|b| (b["s"].clone(), b["s2"].clone()))
2052 .collect();
2053 pairs.sort();
2054 assert_eq!(
2055 pairs,
2056 vec![
2057 ("<http://ex/a>".to_string(), "<http://ex/b>".to_string()),
2058 ("<http://ex/b>".to_string(), "<http://ex/c>".to_string()),
2059 ("<http://ex/b>".to_string(), "<http://ex/x>".to_string()),
2060 ]
2061 );
2062 }
2063
2064 #[test]
2065 fn order_by_numeric_desc_then_limit() {
2066 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2067 let bytes = rete_from(&[
2068 (
2069 "<http://ex/a>",
2070 "<http://ex/age>",
2071 &format!("\"30\"^^{xsd}"),
2072 ),
2073 (
2074 "<http://ex/b>",
2075 "<http://ex/age>",
2076 &format!("\"25\"^^{xsd}"),
2077 ),
2078 (
2079 "<http://ex/c>",
2080 "<http://ex/age>",
2081 &format!("\"40\"^^{xsd}"),
2082 ),
2083 ]);
2084 let rete = Rete::open(&bytes).unwrap();
2085 let q = "PREFIX ex: <http://ex/> \
2087 SELECT ?p WHERE { ?p ex:age ?age } ORDER BY DESC(?age) LIMIT 2";
2088 let (_, sols) = eval_sparql(&rete, q).unwrap();
2089 let ps: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
2090 assert_eq!(ps, vec!["<http://ex/c>", "<http://ex/a>"]); }
2092
2093 #[test]
2094 fn limit_early_out_two_hop_join() {
2095 let bytes = rete_from(&[
2099 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
2100 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
2101 ("<http://ex/B>", "<http://ex/k>", "<http://ex/D>"),
2102 ("<http://ex/C>", "<http://ex/k>", "<http://ex/E>"),
2103 ]);
2104 let rete = Rete::open(&bytes).unwrap();
2105 let q = "PREFIX ex: <http://ex/> SELECT ?x ?z WHERE { ?x ex:k ?y . ?y ex:k ?z }";
2106 let (_, full) = eval_sparql(&rete, q).unwrap();
2107 assert_eq!(full.len(), 3);
2108
2109 let (_, one) = eval_sparql(&rete, &format!("{q} LIMIT 1")).unwrap();
2110 assert_eq!(one.len(), 1);
2111 assert!(one.iter().all(|r| full.contains(r)));
2113
2114 let (_, all) = eval_sparql(&rete, &format!("{q} LIMIT 100")).unwrap();
2116 assert_eq!(all.len(), 3);
2117 let (_, off) = eval_sparql(&rete, &format!("{q} LIMIT 100 OFFSET 2")).unwrap();
2118 assert_eq!(off.len(), 1);
2119 }
2120
2121 #[test]
2122 fn limit_early_out_filter_over_bgp() {
2123 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2126 let bytes = rete_from(&[
2127 ("<http://ex/a>", "<http://ex/n>", &format!("\"10\"^^{xsd}")),
2128 ("<http://ex/b>", "<http://ex/n>", &format!("\"20\"^^{xsd}")),
2129 ("<http://ex/c>", "<http://ex/n>", &format!("\"30\"^^{xsd}")),
2130 ("<http://ex/d>", "<http://ex/n>", &format!("\"40\"^^{xsd}")),
2131 ]);
2132 let rete = Rete::open(&bytes).unwrap();
2133 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { ?p ex:n ?v FILTER(?v > 15) }";
2134 let (_, full) = eval_sparql(&rete, q).unwrap();
2135 assert_eq!(full.len(), 3); let (_, two) = eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap();
2137 assert_eq!(two.len(), 2);
2138 assert!(two.iter().all(|r| full.contains(r)));
2139 }
2140
2141 #[test]
2142 fn distinct_bgp_fast_matches_general() {
2143 let bytes = rete_from(&[
2146 ("<http://ex/a>", "<http://ex/p>", "<http://ex/x>"),
2147 ("<http://ex/b>", "<http://ex/p>", "<http://ex/x>"),
2148 ("<http://ex/b>", "<http://ex/p>", "<http://ex/y>"),
2149 ("<http://ex/c>", "<http://ex/p>", "<http://ex/z>"),
2150 ]);
2151 let rete = Rete::open(&bytes).unwrap();
2152 let q = "PREFIX ex: <http://ex/> SELECT DISTINCT ?o WHERE { ?s ex:p ?o }";
2154 let (proj, sols) = eval_sparql(&rete, q).unwrap();
2155 assert_eq!(proj, vec!["o"]);
2156 let mut os: Vec<&str> = sols.iter().map(|b| b["o"].as_str()).collect();
2157 os.sort();
2158 assert_eq!(os, vec!["<http://ex/x>", "<http://ex/y>", "<http://ex/z>"]);
2159 assert_eq!(
2161 eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap().1.len(),
2162 2
2163 );
2164 let q2 = "PREFIX ex: <http://ex/> SELECT DISTINCT ?s ?o WHERE { ?s ex:p ?o }";
2166 assert_eq!(eval_sparql(&rete, q2).unwrap().1.len(), 4);
2167 }
2168
2169 #[test]
2170 fn limit_caps_solutions() {
2171 let bytes = rete_from(&[
2172 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
2173 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
2174 ("<http://ex/c>", "<http://ex/p>", "<http://ex/3>"),
2175 ]);
2176 let rete = Rete::open(&bytes).unwrap();
2177 let (_, sols) =
2178 eval_sparql(&rete, "SELECT ?x WHERE { ?x <http://ex/p> ?y } LIMIT 2").unwrap();
2179 assert_eq!(sols.len(), 2);
2180 }
2181
2182 #[test]
2189 fn string_filters_see_past_an_embedded_quote() {
2190 let bytes = rete_from(&[
2191 (
2192 "<http://ex/a>",
2193 "<http://ex/label>",
2194 "\"plain Quantum here\"",
2195 ),
2196 (
2197 "<http://ex/b>",
2198 "<http://ex/label>",
2199 r#""He said \"Quantum\" loudly"@en"#,
2200 ),
2201 (
2202 "<http://ex/c>",
2203 "<http://ex/label>",
2204 r#""Theory of \"Quantum\" Gases""#,
2205 ),
2206 ("<http://ex/d>", "<http://ex/label>", "\"no match at all\""),
2207 (
2208 "<http://ex/e>",
2209 "<http://ex/label>",
2210 "\"tab\\there\\nline two\"^^<http://www.w3.org/2001/XMLSchema#string>",
2211 ),
2212 ]);
2213 let rete = Rete::open(&bytes).unwrap();
2214 let subjects = |filter: &str| {
2215 let q = format!("SELECT ?s WHERE {{ ?s <http://ex/label> ?l FILTER({filter}) }}");
2216 let (_, sols) = eval_sparql(&rete, &q).unwrap();
2217 let mut out: Vec<String> = sols.iter().map(|b| b["s"].clone()).collect();
2218 out.sort();
2219 out
2220 };
2221 let (a, b, c, e) = (
2222 "<http://ex/a>",
2223 "<http://ex/b>",
2224 "<http://ex/c>",
2225 "<http://ex/e>",
2226 );
2227 assert_eq!(subjects(r#"CONTAINS(?l, "Quantum")"#), vec![a, b, c]);
2228 assert_eq!(subjects(r#"CONTAINS(?l, "Gases")"#), vec![c]);
2229 assert_eq!(subjects(r#"STRENDS(?l, "loudly")"#), vec![b]);
2230 assert_eq!(subjects(r#"STRSTARTS(?l, "Theory of \"Q")"#), vec![c]);
2231 assert_eq!(subjects(r#"REGEX(?l, "gases$", "i")"#), vec![c]);
2232 assert_eq!(subjects(r#"REGEX(?l, "^line two$", "m")"#), vec![e]);
2233 assert_eq!(subjects(r#"CONTAINS(?l, "\there")"#), vec![e]);
2234 assert_eq!(subjects(r#"LANGMATCHES(LANG(?l), "en")"#), vec![b]);
2235 assert_eq!(subjects(r#"STRAFTER(?l, "Quantum") = "\" Gases""#), vec![c]);
2236 assert_eq!(
2237 subjects(r#"STRBEFORE(?l, " loudly") = "He said \"Quantum\""@en"#),
2238 vec![b]
2239 );
2240 assert_eq!(subjects(r#"STRLEN(?l) = 25"#), vec![c]);
2241 assert_eq!(subjects(r#"STRLEN(?l) = 24"#), vec![b]);
2242 }
2243}