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