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 partition;
24mod path;
25mod ql;
26mod value;
27
28pub use diag::{warnings_json, QueryWarning, WarningSeverity};
29use eval::{ask_solution, instantiate, raw_solutions, run_select, run_select_communities};
30use lower::{lower_pattern, lower_select, parse_query};
31pub use lower::{parse_select, query_predicates};
32pub use partition::PartitionPlan;
33pub(crate) use eval::eval_plan_in;
36
37#[derive(Debug, thiserror::Error)]
38#[non_exhaustive]
39pub enum SparqlError {
40 #[error("parse error: {0}")]
41 Parse(String),
42 #[error("unsupported query feature: {0}")]
43 Unsupported(&'static str),
44 #[error("unsupported query feature: function <{0}> is not implemented")]
47 UnsupportedFunction(String),
48 #[error("SERVICE federation: {0}")]
52 Service(String),
53 #[error("cannot split this query {0}")]
57 Partition(String),
58}
59
60#[derive(Debug, Clone)]
62pub struct Select {
63 pub project: Vec<String>,
65 pub distinct: bool,
67 pub offset: usize,
69 pub limit: Option<usize>,
71 pub group: Option<GroupSpec>,
73 pub extends: Vec<(String, FExpr)>,
76 pub order: Vec<(FExpr, bool)>,
78 pub having: Vec<FExpr>,
80 pub from: Vec<String>,
83 pub from_named: Option<Vec<String>>,
85 pub union_default: bool,
93 pub plan: Plan,
95 pub star_counter: usize,
100}
101
102impl Default for Select {
103 fn default() -> Self {
104 Select {
105 project: Vec::new(),
106 distinct: false,
107 offset: 0,
108 limit: None,
109 group: None,
110 extends: Vec::new(),
111 order: Vec::new(),
112 having: Vec::new(),
113 from: Vec::new(),
114 from_named: None,
115 union_default: false,
116 plan: Plan::Bgp(Vec::new()),
117 star_counter: 0,
118 }
119 }
120}
121
122#[derive(Debug, Clone)]
124pub struct GroupSpec {
125 pub by: Vec<String>,
127 pub aggs: Vec<(String, Agg)>,
129 pub pre: Vec<(String, FExpr)>,
134}
135
136#[derive(Debug, Clone)]
138pub enum Agg {
139 CountStar {
141 distinct: bool,
142 },
143 Count(String, bool),
145 Sum(String),
146 Avg(String),
147 Min(String),
148 Max(String),
149 Sample(String),
151 GroupConcat(String, String, bool),
154}
155
156#[derive(Debug, Clone)]
158#[must_use]
159pub enum Plan {
160 Bgp(Vec<TriplePattern>),
162 Join(Box<Plan>, Box<Plan>),
164 Union(Box<Plan>, Box<Plan>),
166 LeftJoin(Box<Plan>, Box<Plan>, Option<FExpr>),
169 Filter(FExpr, Box<Plan>),
171 Extend(String, FExpr, Box<Plan>),
176 Path(PatternTerm, PathAst, PatternTerm),
178 Values(Vec<String>, Vec<Vec<Option<String>>>),
181 Minus(Box<Plan>, Box<Plan>),
184 Graph(GraphTarget, Box<Plan>),
186 Subquery(Box<Select>),
190 Service {
198 silent: bool,
199 endpoint: String,
201 vars: Vec<String>,
204 query: String,
206 },
207}
208
209#[derive(Debug, Clone)]
211pub enum GraphTarget {
212 Named(String),
214 Var(String),
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum Rep {
221 One,
223 OneOrMore,
225 ZeroOrMore,
227 ZeroOrOne,
229}
230
231#[derive(Debug, Clone)]
234pub enum PathAst {
235 Pred(String, bool),
237 Rep(Box<PathAst>, Rep),
239 Seq(Box<PathAst>, Box<PathAst>),
241 Alt(Box<PathAst>, Box<PathAst>),
243 NegatedSet(Vec<String>, bool),
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub enum Op {
252 Eq,
253 Ne,
254 Lt,
255 Le,
256 Gt,
257 Ge,
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
262pub enum ArithOp {
263 Add,
264 Sub,
265 Mul,
266 Div,
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub enum Builtin {
273 Str,
274 StrLen,
275 UCase,
276 LCase,
277 Abs,
278 Ceil,
279 Floor,
280 Round,
281 Concat,
282 SubStr,
283 StrBefore,
284 StrAfter,
285 Contains,
286 StrStarts,
287 StrEnds,
288 IsIri,
289 IsBlank,
290 IsLiteral,
291 IsNumeric,
292 Datatype,
293 Lang,
294 Regex,
295 LangMatches,
296 StrDt,
297 StrLang,
298 Iri,
299 EncodeForUri,
300 Replace,
301 Md5,
302 Sha1,
303 Sha256,
304 Sha384,
305 Sha512,
306 Year,
307 Month,
308 Day,
309 Hours,
310 Minutes,
311 Seconds,
312 Timezone,
313 Tz,
314 CastInteger,
315 CastDecimal,
316 CastFloat,
317 CastDouble,
318 CastBoolean,
319 CastString,
320 Rand,
321 Uuid,
322 StrUuid,
323 BNode,
324 GeoSfContains,
327 GeoSfWithin,
328 GeoSfIntersects,
329 GeoSfDisjoint,
330 GeoSfEquals,
331 GeoDistance,
332 GeoEnvelope,
333 Geo3Distance,
335 Geo3Contains,
336 Geo3Within,
337 Geo3Adjacent,
338 TripleTerm,
341 IsTriple,
343 Subject,
345 Predicate,
346 Object,
347}
348
349#[derive(Debug, Clone)]
351pub enum FExpr {
352 Var(String),
353 Const(String),
355 Arith(ArithOp, Box<FExpr>, Box<FExpr>),
357 Func(Builtin, Vec<FExpr>),
359 Coalesce(Vec<FExpr>),
361 If(Box<FExpr>, Box<FExpr>, Box<FExpr>),
363 In(Box<FExpr>, Vec<FExpr>),
365 SameTerm(Box<FExpr>, Box<FExpr>),
367 Compare(Op, Box<FExpr>, Box<FExpr>),
368 And(Box<FExpr>, Box<FExpr>),
369 Or(Box<FExpr>, Box<FExpr>),
370 Not(Box<FExpr>),
371 Bound(String),
372 Exists(Box<Plan>),
375}
376
377type ExistsCache = std::collections::HashMap<*const Plan, ExistsEntry>;
380
381struct ExistsEntry {
385 sols: Vec<crate::row::Row>,
386 probe: Option<ExistsProbe>,
387}
388
389struct ExistsProbe {
393 svars: Vec<usize>,
395 jvars: Vec<usize>,
397 keys: std::collections::HashSet<Vec<crate::row::Val>>,
399 partial: Vec<crate::row::Row>,
401}
402
403fn build_exists_probe(b: &crate::row::Row, sols: &[crate::row::Row]) -> ExistsProbe {
406 let mask = crate::row::bound_mask(sols, b.len());
407 let svars: Vec<usize> = (0..b.len()).filter(|&i| mask[i]).collect();
408 let jvars: Vec<usize> = svars.iter().copied().filter(|&i| b[i].is_some()).collect();
409 let mut keys = std::collections::HashSet::new();
410 let mut partial = Vec::new();
411 for s in sols {
412 match jvars
413 .iter()
414 .map(|&i| s[i].clone())
415 .collect::<Option<Vec<crate::row::Val>>>()
416 {
417 Some(k) => {
418 keys.insert(k);
419 }
420 None => partial.push(s.clone()),
421 }
422 }
423 ExistsProbe {
424 svars,
425 jvars,
426 keys,
427 partial,
428 }
429}
430
431fn exists_matches(b: &crate::row::Row, entry: &ExistsEntry) -> bool {
435 let probe = entry.probe.as_ref().unwrap();
436 let bj: Vec<usize> = probe
437 .svars
438 .iter()
439 .copied()
440 .filter(|&i| b[i].is_some())
441 .collect();
442 if bj == probe.jvars {
443 let k: Vec<crate::row::Val> = probe.jvars.iter().map(|&i| b[i].clone().unwrap()).collect();
444 probe.keys.contains(&k)
445 || probe
446 .partial
447 .iter()
448 .any(|s| crate::row::compatible_rows(b, s))
449 } else {
450 entry.sols.iter().any(|s| crate::row::compatible_rows(b, s))
451 }
452}
453
454fn lexical(token: &str) -> String {
461 crate::terms::literal_lexical(token).unwrap_or_else(|| token.to_string())
462}
463
464pub(crate) fn term_number(s: &str) -> Option<f64> {
469 value::numeric(s)
470}
471
472use crate::terms::as_number;
473
474#[derive(Debug, Clone)]
476#[must_use]
477#[non_exhaustive]
478pub enum QueryOutput {
479 Select(Vec<String>, Vec<Binding>),
481 Ask(bool),
483 Construct(Vec<(String, String, String)>),
485}
486
487#[derive(Debug, Clone, PartialEq, Eq)]
490#[must_use]
491#[non_exhaustive]
492pub enum SummaryQueryShape {
493 PredicateCount { predicate: String, variable: String },
495 TripleCount { variable: String },
497 PredicateTotals {
499 predicate_variable: String,
500 count_variable: String,
501 },
502 PredicateList { variable: String },
504 PredicateDistinctCount { variable: String },
506 TripleExists,
508 PredicateExists { predicate: String },
510}
511
512#[derive(Debug, Clone, PartialEq, Eq)]
516pub struct RoutedTriplePattern {
517 pub subject: Option<String>,
518 pub predicate: Option<String>,
519 pub object: Option<String>,
520}
521
522pub fn routed_triple_pattern(query: &str) -> Result<Option<RoutedTriplePattern>, SparqlError> {
527 let parsed = parse_query(query)?;
528 let sel = match parsed {
529 Query::Select {
530 pattern, dataset, ..
531 } => lower_select(&pattern, &dataset)?,
532 Query::Ask { pattern, .. } => lower_pattern(&pattern)?,
533 Query::Construct { pattern, .. } => lower_pattern(&pattern)?,
534 Query::Describe { pattern, .. } => lower_pattern(&pattern)?,
535 };
536 if !sel.from.is_empty() || sel.from_named.is_some() {
537 return Ok(None);
538 }
539 let Plan::Bgp(patterns) = sel.plan else {
540 return Ok(None);
541 };
542 let [tp] = patterns.as_slice() else {
543 return Ok(None);
544 };
545 Ok(Some(RoutedTriplePattern {
546 subject: term_const(&tp.s),
547 predicate: term_const(&tp.p),
548 object: term_const(&tp.o),
549 }))
550}
551
552fn term_const(term: &PatternTerm) -> Option<String> {
553 match term {
554 PatternTerm::Const(t) => Some(t.clone()),
555 PatternTerm::Var(_) => None,
556 }
557}
558
559pub fn summary_query_shape(query: &str) -> Result<Option<SummaryQueryShape>, SparqlError> {
566 let parsed = parse_query(query)?;
567 match parsed {
568 Query::Select {
569 pattern, dataset, ..
570 } => {
571 let sel = lower_select(&pattern, &dataset)?;
572 if !sel.from.is_empty()
573 || sel.from_named.is_some()
574 || sel.offset != 0
575 || sel.limit.is_some()
576 || !sel.order.is_empty()
577 || !sel.having.is_empty()
578 {
579 return Ok(None);
580 }
581 if sel.distinct {
582 if sel.group.is_some() || !sel.extends.is_empty() {
583 return Ok(None);
584 }
585 let [projected] = sel.project.as_slice() else {
586 return Ok(None);
587 };
588 let Some(SummaryPatternShape::AnyPredicate { variable }) =
589 single_summary_pattern(&sel.plan)
590 else {
591 return Ok(None);
592 };
593 return if projected == &variable {
594 Ok(Some(SummaryQueryShape::PredicateList { variable }))
595 } else {
596 Ok(None)
597 };
598 }
599 let Some(group) = &sel.group else {
600 return Ok(None);
601 };
602 if group.aggs.len() != 1 {
603 return Ok(None);
604 }
605 match group.by.as_slice() {
606 [] => match &group.aggs[0].1 {
607 Agg::CountStar { distinct: false } => {
608 let Some(variable) = public_aggregate_variable(&sel, &group.aggs[0].0)
609 else {
610 return Ok(None);
611 };
612 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
613 SummaryPatternShape::Predicate(predicate) => {
614 SummaryQueryShape::PredicateCount {
615 predicate,
616 variable,
617 }
618 }
619 SummaryPatternShape::AnyPredicate { .. } => {
620 SummaryQueryShape::TripleCount { variable }
621 }
622 }))
623 }
624 Agg::Count(counted, true) => {
625 let Some(public_variable) =
626 public_aggregate_variable(&sel, &group.aggs[0].0)
627 else {
628 return Ok(None);
629 };
630 let Some(SummaryPatternShape::AnyPredicate { variable }) =
631 single_summary_pattern(&sel.plan)
632 else {
633 return Ok(None);
634 };
635 if &variable != counted {
636 return Ok(None);
637 }
638 Ok(Some(SummaryQueryShape::PredicateDistinctCount {
639 variable: public_variable,
640 }))
641 }
642 _ => Ok(None),
643 },
644 [group_var] => {
645 if !matches!(group.aggs[0].1, Agg::CountStar { distinct: false }) {
646 return Ok(None);
647 }
648 let Some(SummaryPatternShape::AnyPredicate { variable }) =
649 single_summary_pattern(&sel.plan)
650 else {
651 return Ok(None);
652 };
653 if &variable != group_var {
654 return Ok(None);
655 }
656 let Some(count_variable) =
657 public_group_aggregate_variable(&sel, &group.aggs[0].0, group_var)
658 else {
659 return Ok(None);
660 };
661 Ok(Some(SummaryQueryShape::PredicateTotals {
662 predicate_variable: group_var.clone(),
663 count_variable,
664 }))
665 }
666 _ => Ok(None),
667 }
668 }
669 Query::Ask { pattern, .. } => {
670 let sel = lower_pattern(&pattern)?;
671 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
672 SummaryPatternShape::Predicate(predicate) => {
673 SummaryQueryShape::PredicateExists { predicate }
674 }
675 SummaryPatternShape::AnyPredicate { .. } => SummaryQueryShape::TripleExists,
676 }))
677 }
678 Query::Construct { .. } | Query::Describe { .. } => Ok(None),
679 }
680}
681
682enum SummaryPatternShape {
683 Predicate(String),
684 AnyPredicate { variable: String },
685}
686
687fn single_summary_pattern(plan: &Plan) -> Option<SummaryPatternShape> {
688 let Plan::Bgp(patterns) = plan else {
689 return None;
690 };
691 let [tp] = patterns.as_slice() else {
692 return None;
693 };
694 let (PatternTerm::Var(s), PatternTerm::Var(o)) = (&tp.s, &tp.o) else {
695 return None;
696 };
697 if s == o {
698 return None;
699 }
700 match &tp.p {
701 PatternTerm::Const(p) => Some(SummaryPatternShape::Predicate(p.clone())),
702 PatternTerm::Var(p) if p != s && p != o => Some(SummaryPatternShape::AnyPredicate {
703 variable: p.clone(),
704 }),
705 _ => None,
706 }
707}
708
709fn public_aggregate_variable(sel: &Select, aggregate_var: &str) -> Option<String> {
710 let [projected] = sel.project.as_slice() else {
711 return None;
712 };
713 if projected == aggregate_var {
714 return Some(projected.clone());
715 }
716 sel.extends.iter().find_map(|(var, expr)| match expr {
717 FExpr::Var(source) if var == projected && source == aggregate_var => Some(var.clone()),
718 _ => None,
719 })
720}
721
722fn public_group_aggregate_variable(
723 sel: &Select,
724 aggregate_var: &str,
725 group_var: &str,
726) -> Option<String> {
727 let [projected_group, projected_aggregate] = sel.project.as_slice() else {
728 return None;
729 };
730 if projected_group != group_var {
731 return None;
732 }
733 if projected_aggregate == aggregate_var {
734 return Some(projected_aggregate.clone());
735 }
736 sel.extends.iter().find_map(|(var, expr)| match expr {
737 FExpr::Var(source) if var == projected_aggregate && source == aggregate_var => {
738 Some(var.clone())
739 }
740 _ => None,
741 })
742}
743
744#[derive(Debug, Clone, Copy)]
747pub struct CommunityPartial {
748 pub community: usize,
749 pub subjects: usize,
750 pub rows: usize,
751}
752
753pub type CommunitySelect = (Vec<String>, Vec<Binding>, Vec<CommunityPartial>);
756
757pub fn eval_select_communities(
768 rete: &Rete,
769 query: &str,
770 round: Option<usize>,
771) -> Result<CommunitySelect, SparqlError> {
772 let parsed = parse_query(query)?;
773 let out = match parsed {
774 Query::Select {
775 pattern, dataset, ..
776 } => run_select_communities(rete, &lower_select(&pattern, &dataset)?, round),
777 _ => Err(SparqlError::Unsupported(
778 "community-split evaluation supports SELECT queries only",
779 )),
780 };
781 match rete.take_service_error() {
784 Some(e) => Err(SparqlError::Service(e)),
785 None => out,
786 }
787}
788
789pub fn eval_query(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
791 eval_query_with(rete, query, QueryOpts::default())
792}
793
794pub fn eval_query_reasoned(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
799 eval_query_with(
800 rete,
801 query,
802 QueryOpts {
803 reason: true,
804 ..QueryOpts::default()
805 },
806 )
807}
808
809#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
813pub struct QueryOpts {
814 pub reason: bool,
816 pub union_default_graph: bool,
822}
823
824pub fn eval_query_with(
827 rete: &Rete,
828 query: &str,
829 opts: QueryOpts,
830) -> Result<QueryOutput, SparqlError> {
831 diag::begin();
835 let out = eval_query_inner(rete, query, opts);
836 match rete.take_service_error() {
840 Some(e) => Err(SparqlError::Service(e)),
841 None => out,
842 }
843}
844
845pub fn eval_query_with_warnings(
862 rete: &Rete,
863 query: &str,
864 opts: QueryOpts,
865) -> Result<(QueryOutput, Vec<QueryWarning>), SparqlError> {
866 let out = eval_query_with(rete, query, opts)?;
867 let warnings = diag::finish(&out);
868 Ok((out, warnings))
869}
870
871pub fn partition_plan(rete: &Rete, query: &str, var: &str) -> Result<PartitionPlan, SparqlError> {
887 partition::plan(rete, query, var)
888}
889
890pub fn eval_query_partitioned(
900 rete: &Rete,
901 query: &str,
902 opts: QueryOpts,
903 var: &str,
904 keys: &[String],
905) -> Result<(QueryOutput, Vec<QueryWarning>), SparqlError> {
906 diag::begin();
907 let out = eval_partition_inner(rete, query, opts, partition::var_name(var), keys);
908 let out = match rete.take_service_error() {
909 Some(e) => Err(SparqlError::Service(e)),
910 None => out,
911 }?;
912 let warnings = diag::finish(&out);
913 Ok((out, warnings))
914}
915
916fn eval_partition_inner(
917 rete: &Rete,
918 query: &str,
919 opts: QueryOpts,
920 var: &str,
921 keys: &[String],
922) -> Result<QueryOutput, SparqlError> {
923 let parsed = parse_query(query)?;
924 partition::check(&parsed, var)?;
925 let chunk = |sel: Select| -> Select {
928 let mut sel = maybe_reason(rete, apply_opts(sel, opts), opts.reason);
929 sel.offset = 0;
930 sel.limit = None;
931 sel.plan = partition::seed(sel.plan, var, keys);
932 sel
933 };
934 match parsed {
935 Query::Select {
936 pattern, dataset, ..
937 } => {
938 let (vars, rows) = run_select(rete, &chunk(lower_select(&pattern, &dataset)?));
939 Ok(QueryOutput::Select(vars, rows))
940 }
941 Query::Construct {
942 template, pattern, ..
943 } => {
944 let sel = chunk(lower_pattern(&pattern)?);
945 let (ctx, sols) = raw_solutions(rete, &sel);
946 Ok(QueryOutput::Construct(instantiate(&ctx, &template, &sols)))
947 }
948 _ => Err(SparqlError::Unsupported(
950 "only SELECT and CONSTRUCT can be split",
951 )),
952 }
953}
954
955fn maybe_reason(rete: &Rete, mut sel: Select, reason: bool) -> Select {
958 if reason {
959 let projected = sel.project.clone();
960 sel.plan = ql::reason_rewrite(sel.plan, rete, &projected);
961 }
962 sel
963}
964
965fn apply_opts(mut sel: Select, opts: QueryOpts) -> Select {
970 sel.union_default = opts.union_default_graph;
971 sel
972}
973
974fn eval_query_inner(rete: &Rete, query: &str, opts: QueryOpts) -> Result<QueryOutput, SparqlError> {
975 let reason = opts.reason;
976 let parsed = parse_query(query)?;
977 match parsed {
978 Query::Select {
979 pattern, dataset, ..
980 } => {
981 let (vars, rows) = run_select(
982 rete,
983 &maybe_reason(
984 rete,
985 apply_opts(lower_select(&pattern, &dataset)?, opts),
986 reason,
987 ),
988 );
989 Ok(QueryOutput::Select(vars, rows))
990 }
991 Query::Ask { pattern, .. } => {
992 let sel = maybe_reason(rete, apply_opts(lower_pattern(&pattern)?, opts), reason);
993 Ok(QueryOutput::Ask(ask_solution(rete, &sel)))
994 }
995 Query::Construct {
996 template, pattern, ..
997 } => {
998 let sel = maybe_reason(rete, apply_opts(lower_pattern(&pattern)?, opts), reason);
999 let (ctx, sols) = raw_solutions(rete, &sel);
1000 Ok(QueryOutput::Construct(instantiate(&ctx, &template, &sols)))
1001 }
1002 Query::Describe {
1003 pattern, dataset, ..
1004 } => {
1005 let sel = maybe_reason(
1008 rete,
1009 apply_opts(lower_select(&pattern, &dataset)?, opts),
1010 reason,
1011 );
1012 let (ctx, rows) = raw_solutions(rete, &sel);
1013 let mut resources = std::collections::BTreeSet::new();
1014 for row in &rows {
1015 if sel.project.is_empty() {
1016 for val in row.iter().flatten() {
1017 if let Some(t) = ctx.resolver.str_of(val) {
1018 resources.insert(t.to_string());
1019 }
1020 }
1021 } else {
1022 for v in &sel.project {
1023 if let Some(val) = ctx.slots.slot(v).and_then(|s| row[s].as_ref()) {
1024 if let Some(t) = ctx.resolver.str_of(val) {
1025 resources.insert(t.to_string());
1026 }
1027 }
1028 }
1029 }
1030 }
1031 let mut triples = std::collections::BTreeSet::new();
1032 for r in &resources {
1033 for t in rete.query(Some(r), None, None) {
1034 triples.insert(t);
1035 }
1036 }
1037 Ok(QueryOutput::Construct(triples.into_iter().collect()))
1038 }
1039 }
1040}
1041
1042pub fn eval_sparql(rete: &Rete, query: &str) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
1046 eval_sparql_opts(rete, query, false)
1047}
1048
1049pub fn eval_sparql_reasoned(
1051 rete: &Rete,
1052 query: &str,
1053) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
1054 eval_sparql_opts(rete, query, true)
1055}
1056
1057fn eval_sparql_opts(
1058 rete: &Rete,
1059 query: &str,
1060 reason: bool,
1061) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
1062 let sel = maybe_reason(rete, parse_select(query)?, reason);
1063 let out = run_select(rete, &sel);
1064 match rete.take_service_error() {
1066 Some(e) => Err(SparqlError::Service(e)),
1067 None => Ok(out),
1068 }
1069}
1070
1071pub(crate) fn fmt_num_typed(x: f64) -> String {
1077 if x.fract() == 0.0 {
1078 format!(
1079 "\"{}\"^^<http://www.w3.org/2001/XMLSchema#integer>",
1080 x as i64
1081 )
1082 } else {
1083 let cleaned: f64 = format!("{x:.14e}").parse().unwrap_or(x);
1087 format!("\"{cleaned}\"^^<http://www.w3.org/2001/XMLSchema#decimal>")
1088 }
1089}
1090
1091fn reverse(ast: PathAst) -> PathAst {
1093 match ast {
1094 PathAst::Pred(p, r) => PathAst::Pred(p, !r),
1095 PathAst::Rep(inner, rep) => PathAst::Rep(Box::new(reverse(*inner)), rep),
1096 PathAst::Seq(a, b) => PathAst::Seq(Box::new(reverse(*b)), Box::new(reverse(*a))),
1097 PathAst::Alt(a, b) => PathAst::Alt(Box::new(reverse(*a)), Box::new(reverse(*b))),
1098 PathAst::NegatedSet(s, r) => PathAst::NegatedSet(s, !r),
1099 }
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104 use super::*;
1105 use crate::dictionary::DictionaryBuilder;
1106 use crate::index::GraphIndexBuilder;
1107 use crate::write_file;
1108
1109 fn rete_from(triples: &[(&str, &str, &str)]) -> Vec<u8> {
1110 let mut db = DictionaryBuilder::new();
1111 for (s, p, o) in triples {
1112 db.observe(s, p, o);
1113 }
1114 let dict = db.build();
1115 let mut ib = GraphIndexBuilder::new();
1116 for (s, p, o) in triples {
1117 ib.push(dict.encode(s, p, o).unwrap());
1118 }
1119 write_file(&dict, &ib.build(), false, &[], 0)
1120 }
1121
1122 #[test]
1123 fn parses_select_with_two_patterns() {
1124 let q = r#"
1125 PREFIX ex: <http://ex/>
1126 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }
1127 "#;
1128 let sel = parse_select(q).unwrap();
1129 assert_eq!(sel.project, vec!["x", "z"]);
1130 match &sel.plan {
1131 Plan::Bgp(p) => assert_eq!(p.len(), 2),
1132 other => panic!("expected a BGP plan, got {other:?}"),
1133 }
1134 }
1135
1136 #[test]
1137 fn evaluates_two_hop_select() {
1138 let bytes = rete_from(&[
1139 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1140 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1141 ]);
1142 let rete = Rete::open(&bytes).unwrap();
1143 let q = r#"PREFIX ex: <http://ex/>
1144 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }"#;
1145 let (proj, sols) = eval_sparql(&rete, q).unwrap();
1146 assert_eq!(proj, vec!["x", "z"]);
1147 assert_eq!(sols.len(), 1);
1148 assert_eq!(sols[0]["x"], "<http://ex/Alice>");
1149 assert_eq!(sols[0]["z"], "<http://ex/Carol>");
1150 }
1151
1152 #[test]
1153 fn union_returns_both_sides() {
1154 let bytes = rete_from(&[
1155 ("<http://ex/Alice>", "<http://ex/likes>", "<http://ex/Tea>"),
1156 ("<http://ex/Bob>", "<http://ex/hates>", "<http://ex/Tea>"),
1157 ]);
1158 let rete = Rete::open(&bytes).unwrap();
1159 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { \
1160 { ?p ex:likes ex:Tea } UNION { ?p ex:hates ex:Tea } }";
1161 let (_, sols) = eval_sparql(&rete, q).unwrap();
1162 let mut who: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
1163 who.sort();
1164 assert_eq!(who, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1165 }
1166
1167 #[test]
1168 fn optional_keeps_left_when_right_absent() {
1169 let bytes = rete_from(&[
1171 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1172 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1173 ("<http://ex/Alice>", "<http://ex/email>", "\"a@ex\""),
1174 ]);
1175 let rete = Rete::open(&bytes).unwrap();
1176 let q = "PREFIX ex: <http://ex/> SELECT ?p ?e WHERE { \
1177 ?p ex:name ?n . OPTIONAL { ?p ex:email ?e } }";
1178 let (_, sols) = eval_sparql(&rete, q).unwrap();
1179 assert_eq!(sols.len(), 2, "both people present");
1180 let alice = sols.iter().find(|b| b["p"] == "<http://ex/Alice>").unwrap();
1181 assert_eq!(alice["e"], "\"a@ex\"");
1182 let bob = sols.iter().find(|b| b["p"] == "<http://ex/Bob>").unwrap();
1183 assert!(!bob.contains_key("e"), "Bob has no email binding");
1184 }
1185
1186 #[test]
1187 fn numeric_filter_on_typed_literal() {
1188 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
1190 let bytes = rete_from(&[
1191 (
1192 "<http://ex/Alice>",
1193 "<http://ex/age>",
1194 &format!("\"30\"^^{xsd}"),
1195 ),
1196 (
1197 "<http://ex/Bob>",
1198 "<http://ex/age>",
1199 &format!("\"25\"^^{xsd}"),
1200 ),
1201 ]);
1202 let rete = Rete::open(&bytes).unwrap();
1203 let q = "PREFIX ex: <http://ex/> \
1204 SELECT ?p WHERE { ?p ex:age ?age . FILTER(?age > 27) }";
1205 let (_, sols) = eval_sparql(&rete, q).unwrap();
1206 assert_eq!(sols.len(), 1);
1207 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1208 }
1209
1210 #[test]
1211 fn filter_equality_and_boolean_logic() {
1212 let bytes = rete_from(&[
1213 ("<http://ex/Alice>", "<http://ex/city>", "<http://ex/NYC>"),
1214 ("<http://ex/Bob>", "<http://ex/city>", "<http://ex/LA>"),
1215 ("<http://ex/Carol>", "<http://ex/city>", "<http://ex/NYC>"),
1216 ]);
1217 let rete = Rete::open(&bytes).unwrap();
1218 let q = "PREFIX ex: <http://ex/> \
1219 SELECT ?p WHERE { ?p ex:city ?c . FILTER(?c = ex:NYC && ?p != ex:Carol) }";
1220 let (_, sols) = eval_sparql(&rete, q).unwrap();
1221 assert_eq!(sols.len(), 1);
1222 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1223 }
1224
1225 #[test]
1226 fn distinct_collapses_duplicate_projections() {
1227 let bytes = rete_from(&[
1229 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1230 (
1231 "<http://ex/Alice>",
1232 "<http://ex/knows>",
1233 "<http://ex/Carol>",
1234 ),
1235 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1236 ("<http://ex/Carol>", "<http://ex/knows>", "<http://ex/Dave>"),
1237 ]);
1238 let rete = Rete::open(&bytes).unwrap();
1239 let base = "PREFIX ex: <http://ex/> SELECT {} ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1240
1241 let (_, non_distinct) = eval_sparql(&rete, &base.replace("{}", "")).unwrap();
1242 assert_eq!(non_distinct.len(), 2); let (proj, distinct) = eval_sparql(&rete, &base.replace("{}", "DISTINCT")).unwrap();
1245 assert_eq!(proj, vec!["z"]);
1246 assert_eq!(distinct.len(), 1);
1247 assert_eq!(distinct[0]["z"], "<http://ex/Dave>");
1248 assert!(!distinct[0].contains_key("x"));
1250 }
1251
1252 #[test]
1253 fn transitive_path_reachability() {
1254 let bytes = rete_from(&[
1256 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
1257 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
1258 ("<http://ex/C>", "<http://ex/k>", "<http://ex/D>"),
1259 ]);
1260 let rete = Rete::open(&bytes).unwrap();
1261
1262 let q = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k+ ?y }";
1264 let (_, sols) = eval_sparql(&rete, q).unwrap();
1265 let mut ys: Vec<&str> = sols.iter().map(|b| b["y"].as_str()).collect();
1266 ys.sort();
1267 assert_eq!(ys, vec!["<http://ex/B>", "<http://ex/C>", "<http://ex/D>"]);
1268
1269 let q0 = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k* ?y }";
1271 let (_, s0) = eval_sparql(&rete, q0).unwrap();
1272 assert!(s0.iter().any(|b| b["y"] == "<http://ex/A>"));
1273 assert_eq!(s0.len(), 4); }
1275
1276 #[test]
1277 fn sequence_and_alternative_paths() {
1278 let bytes = rete_from(&[
1280 ("<http://ex/Alice>", "<http://ex/parent>", "<http://ex/Bob>"),
1281 ("<http://ex/Bob>", "<http://ex/parent>", "<http://ex/Carol>"),
1282 (
1283 "<http://ex/Alice>",
1284 "<http://ex/stepparent>",
1285 "<http://ex/Dave>",
1286 ),
1287 ]);
1288 let rete = Rete::open(&bytes).unwrap();
1289
1290 let q = "PREFIX ex: <http://ex/> SELECT ?g WHERE { ex:Alice ex:parent/ex:parent ?g }";
1292 let (_, sols) = eval_sparql(&rete, q).unwrap();
1293 assert_eq!(sols.len(), 1);
1294 assert_eq!(sols[0]["g"], "<http://ex/Carol>");
1295
1296 let qa = "PREFIX ex: <http://ex/> \
1298 SELECT ?p WHERE { ex:Alice ex:parent|ex:stepparent ?p }";
1299 let (_, sa) = eval_sparql(&rete, qa).unwrap();
1300 let mut ps: Vec<&str> = sa.iter().map(|b| b["p"].as_str()).collect();
1301 ps.sort();
1302 assert_eq!(ps, vec!["<http://ex/Bob>", "<http://ex/Dave>"]);
1303 }
1304
1305 #[test]
1306 fn group_concat_aggregate() {
1307 let bytes = rete_from(&[
1308 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1309 (
1310 "<http://ex/Alice>",
1311 "<http://ex/knows>",
1312 "<http://ex/Carol>",
1313 ),
1314 ]);
1315 let rete = Rete::open(&bytes).unwrap();
1316 let q = "PREFIX ex: <http://ex/> \
1317 SELECT (GROUP_CONCAT(?f; SEPARATOR=\"|\") AS ?fs) WHERE { ex:Alice ex:knows ?f }";
1318 let (_, sols) = eval_sparql(&rete, q).unwrap();
1319 assert_eq!(sols.len(), 1);
1320 let fs = sols[0]["fs"].trim_matches('"');
1322 let mut parts: Vec<&str> = fs.split('|').collect();
1323 parts.sort();
1324 assert_eq!(parts, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1325 }
1326
1327 #[test]
1328 fn group_by_having() {
1329 let bytes = rete_from(&[
1331 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1332 (
1333 "<http://ex/Alice>",
1334 "<http://ex/knows>",
1335 "<http://ex/Carol>",
1336 ),
1337 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1338 ]);
1339 let rete = Rete::open(&bytes).unwrap();
1340 let q = "PREFIX ex: <http://ex/> SELECT ?p (COUNT(?f) AS ?n) \
1342 WHERE { ?p ex:knows ?f } GROUP BY ?p HAVING (COUNT(?f) > 1)";
1343 let (_, sols) = eval_sparql(&rete, q).unwrap();
1344 assert_eq!(sols.len(), 1);
1345 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1346 assert_eq!(
1347 sols[0]["n"],
1348 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1349 );
1350 }
1351
1352 #[test]
1353 fn count_group_by() {
1354 let bytes = rete_from(&[
1356 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1357 (
1358 "<http://ex/Alice>",
1359 "<http://ex/knows>",
1360 "<http://ex/Carol>",
1361 ),
1362 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1363 ]);
1364 let rete = Rete::open(&bytes).unwrap();
1365 let q = "PREFIX ex: <http://ex/> \
1366 SELECT ?p (COUNT(?f) AS ?n) WHERE { ?p ex:knows ?f } GROUP BY ?p";
1367 let (_, sols) = eval_sparql(&rete, q).unwrap();
1368 let mut counts: Vec<(String, String)> = sols
1369 .iter()
1370 .map(|b| (b["p"].clone(), b["n"].clone()))
1371 .collect();
1372 counts.sort();
1373 assert_eq!(
1374 counts,
1375 vec![
1376 (
1377 "<http://ex/Alice>".into(),
1378 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1379 ),
1380 (
1381 "<http://ex/Bob>".into(),
1382 "\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1383 ),
1384 ]
1385 );
1386 }
1387
1388 #[test]
1389 fn global_count_star() {
1390 let bytes = rete_from(&[
1391 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
1392 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
1393 ]);
1394 let rete = Rete::open(&bytes).unwrap();
1395 let (_, sols) = eval_sparql(&rete, "SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1396 assert_eq!(sols.len(), 1);
1397 assert_eq!(
1398 sols[0]["n"],
1399 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1400 );
1401 }
1402
1403 #[test]
1404 fn summary_query_shape_classifies_only_exact_predicate_totals() {
1405 let count = summary_query_shape(
1406 "PREFIX ex: <http://ex/> SELECT (COUNT(*) AS ?n) WHERE { ?s ex:p ?o }",
1407 )
1408 .unwrap();
1409 assert_eq!(
1410 count,
1411 Some(SummaryQueryShape::PredicateCount {
1412 predicate: "<http://ex/p>".into(),
1413 variable: "n".into(),
1414 })
1415 );
1416
1417 let total = summary_query_shape("SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1418 assert_eq!(
1419 total,
1420 Some(SummaryQueryShape::TripleCount {
1421 variable: "n".into(),
1422 })
1423 );
1424
1425 let by_pred =
1426 summary_query_shape("SELECT ?p (COUNT(*) AS ?n) WHERE { ?s ?p ?o } GROUP BY ?p")
1427 .unwrap();
1428 assert_eq!(
1429 by_pred,
1430 Some(SummaryQueryShape::PredicateTotals {
1431 predicate_variable: "p".into(),
1432 count_variable: "n".into(),
1433 })
1434 );
1435
1436 let predicates = summary_query_shape("SELECT DISTINCT ?p WHERE { ?s ?p ?o }").unwrap();
1437 assert_eq!(
1438 predicates,
1439 Some(SummaryQueryShape::PredicateList {
1440 variable: "p".into(),
1441 })
1442 );
1443
1444 let predicate_count =
1445 summary_query_shape("SELECT (COUNT(DISTINCT ?p) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1446 assert_eq!(
1447 predicate_count,
1448 Some(SummaryQueryShape::PredicateDistinctCount {
1449 variable: "n".into(),
1450 })
1451 );
1452
1453 let ask = summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o }").unwrap();
1454 assert_eq!(
1455 ask,
1456 Some(SummaryQueryShape::PredicateExists {
1457 predicate: "<http://ex/p>".into(),
1458 })
1459 );
1460
1461 let any_ask = summary_query_shape("ASK { ?s ?p ?o }").unwrap();
1462 assert_eq!(any_ask, Some(SummaryQueryShape::TripleExists));
1463
1464 let constrained =
1465 summary_query_shape("PREFIX ex: <http://ex/> ASK { ex:a ex:p ?o }").unwrap();
1466 assert_eq!(constrained, None);
1467
1468 let filtered =
1469 summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o FILTER(?s = ?o) }")
1470 .unwrap();
1471 assert_eq!(filtered, None);
1472 }
1473
1474 #[test]
1475 fn filter_exists_and_not_exists() {
1476 let bytes = rete_from(&[
1478 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1479 (
1480 "<http://ex/Alice>",
1481 "<http://ex/knows>",
1482 "<http://ex/Carol>",
1483 ),
1484 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1485 ]);
1486 let rete = Rete::open(&bytes).unwrap();
1487
1488 let q_exists = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1490 ex:Alice ex:knows ?f . FILTER EXISTS { ?f ex:knows ?x } }";
1491 let (_, e) = eval_sparql(&rete, q_exists).unwrap();
1492 assert_eq!(
1493 e.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1494 vec!["<http://ex/Bob>"]
1495 );
1496
1497 let q_not = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1499 ex:Alice ex:knows ?f . FILTER NOT EXISTS { ?f ex:knows ?x } }";
1500 let (_, n) = eval_sparql(&rete, q_not).unwrap();
1501 assert_eq!(
1502 n.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1503 vec!["<http://ex/Carol>"]
1504 );
1505 }
1506
1507 #[test]
1508 fn ask_repeated_variable_pattern() {
1509 let yes = rete_from(&[
1512 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1513 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Bob>"),
1514 ]);
1515 let rete = Rete::open(&yes).unwrap();
1516 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1517 QueryOutput::Ask(b) => assert!(b, "Bob knows himself"),
1518 other => panic!("expected Ask, got {other:?}"),
1519 }
1520 let no = rete_from(&[("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>")]);
1523 let rete = Rete::open(&no).unwrap();
1524 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1525 QueryOutput::Ask(b) => assert!(!b, "nobody knows themselves"),
1526 other => panic!("expected Ask, got {other:?}"),
1527 }
1528 }
1529
1530 #[test]
1531 fn minus_excludes_compatible() {
1532 let bytes = rete_from(&[
1534 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1535 (
1536 "<http://ex/Alice>",
1537 "<http://ex/knows>",
1538 "<http://ex/Carol>",
1539 ),
1540 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1541 ]);
1542 let rete = Rete::open(&bytes).unwrap();
1543 let q = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1545 ex:Alice ex:knows ?f . MINUS { ?f ex:knows ?x } }";
1546 let (_, sols) = eval_sparql(&rete, q).unwrap();
1547 let fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1548 assert_eq!(fs, vec!["<http://ex/Carol>"]);
1549 }
1550
1551 #[test]
1552 fn filter_exists_disjoint_variable() {
1553 let bytes = rete_from(&[
1558 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1559 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1560 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1561 ]);
1562 let rete = Rete::open(&bytes).unwrap();
1563 let q_none = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1564 ?x ex:a ex:Person FILTER NOT EXISTS { ?y ex:a ex:Drink } }";
1565 assert!(eval_sparql(&rete, q_none).unwrap().1.is_empty());
1566 let q_all = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1567 ?x ex:a ex:Person FILTER EXISTS { ?y ex:a ex:Drink } }";
1568 assert_eq!(eval_sparql(&rete, q_all).unwrap().1.len(), 2);
1569 }
1570
1571 #[test]
1572 fn minus_disjoint_domain_keeps_all() {
1573 let bytes = rete_from(&[
1577 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1578 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1579 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1580 ]);
1581 let rete = Rete::open(&bytes).unwrap();
1582 let q = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1583 ?x ex:a ex:Person MINUS { ?y ex:a ex:Drink } }";
1584 let (_, sols) = eval_sparql(&rete, q).unwrap();
1585 let mut xs: Vec<&str> = sols.iter().map(|b| b["x"].as_str()).collect();
1586 xs.sort();
1587 assert_eq!(xs, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1588 }
1589
1590 #[test]
1591 fn values_pushdown_selects_subset() {
1592 let bytes = rete_from(&[
1595 ("<http://ex/a>", "<http://ex/d>", "<http://ex/Bio>"),
1596 ("<http://ex/b>", "<http://ex/d>", "<http://ex/Phys>"),
1597 ("<http://ex/c>", "<http://ex/d>", "<http://ex/Chem>"),
1598 ]);
1599 let rete = Rete::open(&bytes).unwrap();
1600 let q = "PREFIX ex: <http://ex/> SELECT ?p ?disc WHERE { \
1601 VALUES ?disc { ex:Bio ex:Phys } ?p ex:d ?disc }";
1602 let (_, sols) = eval_sparql(&rete, q).unwrap();
1603 let mut got: Vec<(String, String)> = sols
1604 .iter()
1605 .map(|b| (b["p"].clone(), b["disc"].clone()))
1606 .collect();
1607 got.sort();
1608 assert_eq!(
1609 got,
1610 vec![
1611 ("<http://ex/a>".into(), "<http://ex/Bio>".into()),
1612 ("<http://ex/b>".into(), "<http://ex/Phys>".into()),
1613 ]
1614 );
1615 }
1616
1617 #[test]
1618 fn values_inline_data_joins() {
1619 let bytes = rete_from(&[
1620 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1621 (
1622 "<http://ex/Alice>",
1623 "<http://ex/knows>",
1624 "<http://ex/Carol>",
1625 ),
1626 ("<http://ex/Dave>", "<http://ex/knows>", "<http://ex/Eve>"),
1627 ]);
1628 let rete = Rete::open(&bytes).unwrap();
1629 let q = "PREFIX ex: <http://ex/> \
1631 SELECT ?f WHERE { VALUES ?p { ex:Alice } ?p ex:knows ?f }";
1632 let (_, sols) = eval_sparql(&rete, q).unwrap();
1633 let mut fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1634 fs.sort();
1635 assert_eq!(fs, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1636 }
1637
1638 #[test]
1639 fn graph_queries_over_named_graphs() {
1640 use crate::write_dataset;
1641 let mut db = DictionaryBuilder::new();
1643 let edges = [
1644 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1645 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1646 ];
1647 for (s, p, o) in edges {
1648 db.observe(s, p, o);
1649 }
1650 let dict = db.build();
1651 let mut social = GraphIndexBuilder::new();
1652 social.push(dict.encode(edges[0].0, edges[0].1, edges[0].2).unwrap());
1653 let mut profile = GraphIndexBuilder::new();
1654 profile.push(dict.encode(edges[1].0, edges[1].1, edges[1].2).unwrap());
1655 let named = vec![
1656 ("<http://ex/social>".to_string(), social.build()),
1657 ("<http://ex/profile>".to_string(), profile.build()),
1658 ];
1659 let bytes = write_dataset(
1660 &dict,
1661 &GraphIndexBuilder::new().build(),
1662 &named,
1663 true,
1664 &[],
1665 0,
1666 );
1667 let rete = Rete::open(&bytes).unwrap();
1668
1669 let q = "PREFIX ex: <http://ex/> \
1671 SELECT ?f WHERE { GRAPH ex:social { ex:Alice ex:knows ?f } }";
1672 let (_, s) = eval_sparql(&rete, q).unwrap();
1673 assert_eq!(s.len(), 1);
1674 assert_eq!(s[0]["f"], "<http://ex/Bob>");
1675
1676 let q2 = "PREFIX ex: <http://ex/> \
1678 SELECT ?g WHERE { GRAPH ?g { ex:Bob ex:age ?a } }";
1679 let (_, s2) = eval_sparql(&rete, q2).unwrap();
1680 assert_eq!(s2.len(), 1);
1681 assert_eq!(s2[0]["g"], "<http://ex/profile>");
1682
1683 let q3 = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1686 GRAPH ex:profile { ?f ex:age ?a . FILTER EXISTS { ?f ex:age ?a2 } } }";
1687 assert_eq!(eval_sparql(&rete, q3).unwrap().1.len(), 1);
1688 let q4 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
1689 GRAPH ex:social { ?s ex:knows ?o . FILTER NOT EXISTS { ?s ex:age ?a } } }";
1690 assert_eq!(eval_sparql(&rete, q4).unwrap().1.len(), 1);
1692 }
1693
1694 #[test]
1695 fn from_unions_named_graphs() {
1696 use crate::write_dataset;
1697 let mut db = DictionaryBuilder::new();
1700 let t = [
1701 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1702 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1703 ];
1704 for (s, p, o) in t {
1705 db.observe(s, p, o);
1706 }
1707 let dict = db.build();
1708 let mut g1 = GraphIndexBuilder::new();
1709 g1.push(dict.encode(t[0].0, t[0].1, t[0].2).unwrap());
1710 let mut g2 = GraphIndexBuilder::new();
1711 g2.push(dict.encode(t[1].0, t[1].1, t[1].2).unwrap());
1712 let named = vec![
1713 ("<http://ex/social>".to_string(), g1.build()),
1714 ("<http://ex/profile>".to_string(), g2.build()),
1715 ];
1716 let bytes = write_dataset(
1717 &dict,
1718 &GraphIndexBuilder::new().build(),
1719 &named,
1720 true,
1721 &[],
1722 0,
1723 );
1724 let rete = Rete::open(&bytes).unwrap();
1725
1726 let q0 = "PREFIX ex: <http://ex/> SELECT ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1728 assert!(eval_sparql(&rete, q0).unwrap().1.is_empty());
1729
1730 let q = "PREFIX ex: <http://ex/> \
1732 SELECT ?z FROM ex:social FROM ex:profile \
1733 WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1734 let (_, s) = eval_sparql(&rete, q).unwrap();
1735 assert_eq!(s.len(), 1);
1736 assert_eq!(s[0]["z"], "<http://ex/Carol>");
1737
1738 let qn = "PREFIX ex: <http://ex/> SELECT ?g FROM NAMED ex:social \
1740 WHERE { GRAPH ?g { ?x ex:knows ?y } }";
1741 let (_, sn) = eval_sparql(&rete, qn).unwrap();
1742 let gs: Vec<&str> = sn.iter().map(|b| b["g"].as_str()).collect();
1743 assert_eq!(gs, vec!["<http://ex/social>"]); }
1745
1746 #[test]
1747 fn union_default_graph_opt_in() {
1748 use crate::write_dataset;
1749 let mut db = DictionaryBuilder::new();
1753 let t = [
1754 ("<http://ex/Root>", "<http://ex/knows>", "<http://ex/Alice>"),
1755 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1756 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1757 ];
1758 for (s, p, o) in t {
1759 db.observe(s, p, o);
1760 }
1761 let dict = db.build();
1762 let enc = |i: usize| dict.encode(t[i].0, t[i].1, t[i].2).unwrap();
1763 let mut def = GraphIndexBuilder::new();
1764 def.push(enc(0));
1765 let mut g1 = GraphIndexBuilder::new();
1766 g1.push(enc(1));
1767 let mut g2 = GraphIndexBuilder::new();
1768 g2.push(enc(1)); g2.push(enc(2));
1770 let named = vec![
1771 ("<http://ex/social>".to_string(), g1.build()),
1772 ("<http://ex/profile>".to_string(), g2.build()),
1773 ];
1774 let bytes = write_dataset(&dict, &def.build(), &named, true, &[], 0);
1775 let rete = Rete::open(&bytes).unwrap();
1776 let union = QueryOpts {
1777 union_default_graph: true,
1778 ..QueryOpts::default()
1779 };
1780 let rows = |out: QueryOutput| match out {
1781 QueryOutput::Select(_, rows) => rows,
1782 other => panic!("expected Select, got {other:?}"),
1783 };
1784
1785 let q = "PREFIX ex: <http://ex/> SELECT ?s ?o WHERE { ?s ex:knows ?o }";
1789 assert_eq!(rows(eval_query(&rete, q).unwrap()).len(), 1);
1790
1791 let got = rows(eval_query_with(&rete, q, union).unwrap());
1795 assert_eq!(got.len(), 3);
1796 assert!(got.iter().any(|b| b["s"] == "<http://ex/Root>"));
1797
1798 let qj = "PREFIX ex: <http://ex/> SELECT ?z WHERE { \
1800 ex:Root ex:knows ?x . ?x ex:knows ?y . ?y ex:knows ?z }";
1801 let sj = rows(eval_query_with(&rete, qj, union).unwrap());
1802 assert_eq!(sj.len(), 1);
1803 assert_eq!(sj[0]["z"], "<http://ex/Carol>");
1804
1805 let qg = "PREFIX ex: <http://ex/> SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ex:knows ?o } }";
1807 assert_eq!(rows(eval_query_with(&rete, qg, union).unwrap()).len(), 2);
1808
1809 let qf = "PREFIX ex: <http://ex/> SELECT ?s ?o FROM ex:social WHERE { ?s ex:knows ?o }";
1812 let sf = rows(eval_query_with(&rete, qf, union).unwrap());
1813 assert_eq!(sf.len(), 1);
1814 assert_eq!(sf[0]["s"], "<http://ex/Alice>");
1815
1816 let mut db2 = DictionaryBuilder::new();
1819 db2.observe(t[1].0, t[1].1, t[1].2);
1820 let dict2 = db2.build();
1821 let mut only = GraphIndexBuilder::new();
1822 only.push(dict2.encode(t[1].0, t[1].1, t[1].2).unwrap());
1823 let named2 = vec![("<http://ex/only>".to_string(), only.build())];
1824 let bytes2 = write_dataset(
1825 &dict2,
1826 &GraphIndexBuilder::new().build(),
1827 &named2,
1828 true,
1829 &[],
1830 0,
1831 );
1832 let rete2 = Rete::open(&bytes2).unwrap();
1833 assert_eq!(rows(eval_query(&rete2, q).unwrap()).len(), 0); assert_eq!(rows(eval_query_with(&rete2, q, union).unwrap()).len(), 1);
1835 }
1836
1837 #[test]
1838 fn describe_resource() {
1839 let bytes = rete_from(&[
1840 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1841 ("<http://ex/Alice>", "<http://ex/age>", "\"30\""),
1842 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1843 ]);
1844 let rete = Rete::open(&bytes).unwrap();
1845 match eval_query(&rete, "DESCRIBE <http://ex/Alice>").unwrap() {
1847 QueryOutput::Construct(t) => assert_eq!(t.len(), 2),
1848 other => panic!("expected Construct, got {other:?}"),
1849 }
1850 let q = "PREFIX ex: <http://ex/> DESCRIBE ?x WHERE { ?x ex:age ?a }";
1852 match eval_query(&rete, q).unwrap() {
1853 QueryOutput::Construct(t) => {
1854 assert_eq!(t.len(), 3);
1856 }
1857 other => panic!("expected Construct, got {other:?}"),
1858 }
1859 }
1860
1861 #[test]
1862 fn ask_and_construct() {
1863 let bytes = rete_from(&[
1864 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1865 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1866 ]);
1867 let rete = Rete::open(&bytes).unwrap();
1868
1869 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:knows ?b }").unwrap() {
1871 QueryOutput::Ask(b) => assert!(b),
1872 other => panic!("expected Ask, got {other:?}"),
1873 }
1874 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:likes ?b }").unwrap() {
1875 QueryOutput::Ask(b) => assert!(!b),
1876 other => panic!("expected Ask, got {other:?}"),
1877 }
1878
1879 let q = "PREFIX ex: <http://ex/> \
1881 CONSTRUCT { ?b ex:knownBy ?a } WHERE { ?a ex:knows ?b }";
1882 match eval_query(&rete, q).unwrap() {
1883 QueryOutput::Construct(mut triples) => {
1884 triples.sort();
1885 assert_eq!(triples.len(), 2);
1886 assert!(triples.contains(&(
1887 "<http://ex/Bob>".into(),
1888 "<http://ex/knownBy>".into(),
1889 "<http://ex/Alice>".into(),
1890 )));
1891 }
1892 other => panic!("expected Construct, got {other:?}"),
1893 }
1894 }
1895
1896 #[test]
1897 fn substr_strbefore_strafter() {
1898 let bytes = rete_from(&[("<http://ex/a>", "<http://ex/name>", "\"Alice Smith\"")]);
1899 let rete = Rete::open(&bytes).unwrap();
1900 let q = "PREFIX ex: <http://ex/> SELECT ?first ?last ?ini WHERE { \
1901 ?p ex:name ?n . \
1902 BIND(STRBEFORE(?n, \" \") AS ?first) \
1903 BIND(STRAFTER(?n, \" \") AS ?last) \
1904 BIND(SUBSTR(?n, 1, 1) AS ?ini) }";
1905 let (_, sols) = eval_sparql(&rete, q).unwrap();
1906 assert_eq!(sols[0]["first"], "\"Alice\"");
1908 assert_eq!(sols[0]["last"], "\"Smith\"");
1909 assert_eq!(sols[0]["ini"], "\"A\"");
1910 }
1911
1912 #[test]
1913 fn concat_and_coalesce() {
1914 let bytes = rete_from(&[
1916 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1917 ("<http://ex/Alice>", "<http://ex/nick>", "\"Al\""),
1918 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1919 ]);
1920 let rete = Rete::open(&bytes).unwrap();
1921 let q = "PREFIX ex: <http://ex/> SELECT ?label WHERE { \
1923 ?p ex:name ?name . OPTIONAL { ?p ex:nick ?nick } \
1924 BIND(CONCAT(\"@\", COALESCE(?nick, ?name)) AS ?label) }";
1925 let (_, sols) = eval_sparql(&rete, q).unwrap();
1926 let mut labels: Vec<&str> = sols.iter().map(|b| b["label"].as_str()).collect();
1927 labels.sort();
1928 assert_eq!(labels, vec!["\"@Al\"", "\"@Bob\""]);
1929 }
1930
1931 #[test]
1932 fn builtin_functions() {
1933 let bytes = rete_from(&[
1934 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice Smith\""),
1935 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob Jones\""),
1936 ]);
1937 let rete = Rete::open(&bytes).unwrap();
1938 let q = "PREFIX ex: <http://ex/> SELECT ?p ?len WHERE { \
1940 ?p ex:name ?n . FILTER(CONTAINS(?n, \"Smith\")) BIND(STRLEN(?n) AS ?len) }";
1941 let (_, sols) = eval_sparql(&rete, q).unwrap();
1942 assert_eq!(sols.len(), 1);
1943 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1944 assert_eq!(
1945 sols[0]["len"],
1946 "\"11\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1947 ); }
1949
1950 #[test]
1951 fn geosparql_filter_and_functions() {
1952 let wkt = "\"POLYGON((0 0,10 0,10 10,0 10,0 0))\"^^\
1953 <http://www.opengis.net/ont/geosparql#wktLiteral>";
1954 let bytes = rete_from(&[
1955 (
1956 "<http://ex/f>",
1957 "<http://www.opengis.net/ont/geosparql#hasGeometry>",
1958 "<http://ex/f/g>",
1959 ),
1960 (
1961 "<http://ex/f/g>",
1962 "<http://www.opengis.net/ont/geosparql#asWKT>",
1963 wkt,
1964 ),
1965 (
1966 "<http://ex/f>",
1967 "<http://ex/year>",
1968 "\"1815\"^^<http://www.w3.org/2001/XMLSchema#integer>",
1969 ),
1970 ]);
1971 let rete = Rete::open(&bytes).unwrap();
1972 let pre = "PREFIX geo: <http://www.opengis.net/ont/geosparql#> \
1973 PREFIX geof: <http://www.opengis.net/def/function/geosparql/> \
1974 PREFIX uom: <http://www.opengis.net/def/uom/OGC/1.0/> PREFIX ex: <http://ex/> ";
1975
1976 let (_, s) = eval_sparql(
1978 &rete,
1979 &format!(
1980 "{pre}SELECT ?f WHERE {{ ?f ex:year ?y ; \
1981 geo:hasGeometry/geo:asWKT ?w . \
1982 FILTER(?y = 1815 && geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral)) }}"
1983 ),
1984 )
1985 .unwrap();
1986 assert_eq!(s.len(), 1, "point inside the polygon in year 1815");
1987 assert_eq!(s[0]["f"], "<http://ex/f>");
1988
1989 let (_, s) = eval_sparql(
1991 &rete,
1992 &format!(
1993 "{pre}SELECT ?f WHERE {{ \
1994 ?f geo:hasGeometry/geo:asWKT ?w . \
1995 FILTER(geof:sfContains(?w, \"POINT(50 50)\"^^geo:wktLiteral)) }}"
1996 ),
1997 )
1998 .unwrap();
1999 assert!(s.is_empty());
2000
2001 let (_, s) = eval_sparql(
2003 &rete,
2004 &format!(
2005 "{pre}SELECT ?f WHERE {{ \
2006 ?f geo:hasGeometry/geo:asWKT ?w . \
2007 FILTER(geof:sfWithin(\"POINT(5 5)\"^^geo:wktLiteral, ?w)) }}"
2008 ),
2009 )
2010 .unwrap();
2011 assert_eq!(s.len(), 1);
2012
2013 let (_, s) = eval_sparql(
2015 &rete,
2016 &format!(
2017 "{pre}SELECT ?f WHERE {{ \
2018 ?f geo:hasGeometry/geo:asWKT ?w . \
2019 FILTER(geof:sfContains(?w, \"garbage\"^^geo:wktLiteral)) }}"
2020 ),
2021 )
2022 .unwrap();
2023 assert!(s.is_empty());
2024
2025 let (_, s) = eval_sparql(
2027 &rete,
2028 &format!(
2029 "{pre}SELECT ?hit WHERE {{ \
2030 ?f geo:hasGeometry/geo:asWKT ?w . \
2031 BIND(geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral) AS ?hit) }}"
2032 ),
2033 )
2034 .unwrap();
2035 assert_eq!(
2036 s[0]["hit"],
2037 "\"true\"^^<http://www.w3.org/2001/XMLSchema#boolean>"
2038 );
2039
2040 let (_, s) = eval_sparql(
2042 &rete,
2043 &format!(
2044 "{pre}SELECT ?d WHERE {{ BIND(geof:distance(\
2045 \"POINT(0 0)\"^^geo:wktLiteral, \"POINT(0 1)\"^^geo:wktLiteral, uom:metre) AS ?d) }}"
2046 ),
2047 )
2048 .unwrap();
2049 assert!(
2050 s[0]["d"].ends_with("XMLSchema#double>"),
2051 "distance is xsd:double: {}",
2052 s[0]["d"]
2053 );
2054 let (_, s) = eval_sparql(
2055 &rete,
2056 &format!(
2057 "{pre}SELECT ?e WHERE {{ \
2058 ?f geo:hasGeometry/geo:asWKT ?w . BIND(geof:envelope(?w) AS ?e) }}"
2059 ),
2060 )
2061 .unwrap();
2062 assert!(s[0]["e"].contains("wktLiteral") && s[0]["e"].contains("POLYGON"));
2063
2064 assert!(eval_sparql(
2066 &rete,
2067 &format!(
2068 "{pre}SELECT ?x WHERE {{ \
2069 BIND(geof:buffer(\"POINT(0 0)\"^^geo:wktLiteral, 1) AS ?x) }}"
2070 )
2071 )
2072 .is_err());
2073 }
2074
2075 #[test]
2076 fn bind_arithmetic() {
2077 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2078 let bytes = rete_from(&[(
2079 "<http://ex/a>",
2080 "<http://ex/age>",
2081 &format!("\"30\"^^{xsd}"),
2082 )]);
2083 let rete = Rete::open(&bytes).unwrap();
2084 let q = "PREFIX ex: <http://ex/> \
2086 SELECT ?next WHERE { ?p ex:age ?age . BIND(?age + 1 AS ?next) FILTER(?age * 2 > 50) }";
2087 let (_, sols) = eval_sparql(&rete, q).unwrap();
2088 assert_eq!(sols.len(), 1);
2089 assert_eq!(
2090 sols[0]["next"],
2091 "\"31\"^^<http://www.w3.org/2001/XMLSchema#integer>"
2092 );
2093 }
2094
2095 #[test]
2096 fn bind_value_is_visible_to_a_following_filter_and_join() {
2097 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2101 let n = |v: i32| format!("\"{v}\"^^{xsd}");
2102 let bytes = rete_from(&[
2103 ("<http://ex/a>", "<http://ex/v>", &n(1)),
2104 ("<http://ex/b>", "<http://ex/v>", &n(2)),
2105 ("<http://ex/c>", "<http://ex/v>", &n(3)),
2106 ("<http://ex/x>", "<http://ex/v>", &n(3)),
2108 ]);
2109 let rete = Rete::open(&bytes).unwrap();
2110
2111 let q1 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
2113 ?s ex:v ?o . BIND(?o + 1 AS ?z) FILTER(?z = 3) }";
2114 let (_, s1) = eval_sparql(&rete, q1).unwrap();
2115 assert_eq!(s1.len(), 1, "only b (2+1=3) passes");
2116 assert_eq!(s1[0]["s"], "<http://ex/b>");
2117
2118 let q2 = "PREFIX ex: <http://ex/> SELECT ?s ?s2 WHERE { \
2120 ?s ex:v ?o . BIND(?o + 1 AS ?z) ?s2 ex:v ?z }";
2121 let (_, s2) = eval_sparql(&rete, q2).unwrap();
2122 let mut pairs: Vec<(String, String)> = s2
2124 .iter()
2125 .map(|b| (b["s"].clone(), b["s2"].clone()))
2126 .collect();
2127 pairs.sort();
2128 assert_eq!(
2129 pairs,
2130 vec![
2131 ("<http://ex/a>".to_string(), "<http://ex/b>".to_string()),
2132 ("<http://ex/b>".to_string(), "<http://ex/c>".to_string()),
2133 ("<http://ex/b>".to_string(), "<http://ex/x>".to_string()),
2134 ]
2135 );
2136 }
2137
2138 #[test]
2139 fn order_by_numeric_desc_then_limit() {
2140 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2141 let bytes = rete_from(&[
2142 (
2143 "<http://ex/a>",
2144 "<http://ex/age>",
2145 &format!("\"30\"^^{xsd}"),
2146 ),
2147 (
2148 "<http://ex/b>",
2149 "<http://ex/age>",
2150 &format!("\"25\"^^{xsd}"),
2151 ),
2152 (
2153 "<http://ex/c>",
2154 "<http://ex/age>",
2155 &format!("\"40\"^^{xsd}"),
2156 ),
2157 ]);
2158 let rete = Rete::open(&bytes).unwrap();
2159 let q = "PREFIX ex: <http://ex/> \
2161 SELECT ?p WHERE { ?p ex:age ?age } ORDER BY DESC(?age) LIMIT 2";
2162 let (_, sols) = eval_sparql(&rete, q).unwrap();
2163 let ps: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
2164 assert_eq!(ps, vec!["<http://ex/c>", "<http://ex/a>"]); }
2166
2167 #[test]
2168 fn limit_early_out_two_hop_join() {
2169 let bytes = rete_from(&[
2173 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
2174 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
2175 ("<http://ex/B>", "<http://ex/k>", "<http://ex/D>"),
2176 ("<http://ex/C>", "<http://ex/k>", "<http://ex/E>"),
2177 ]);
2178 let rete = Rete::open(&bytes).unwrap();
2179 let q = "PREFIX ex: <http://ex/> SELECT ?x ?z WHERE { ?x ex:k ?y . ?y ex:k ?z }";
2180 let (_, full) = eval_sparql(&rete, q).unwrap();
2181 assert_eq!(full.len(), 3);
2182
2183 let (_, one) = eval_sparql(&rete, &format!("{q} LIMIT 1")).unwrap();
2184 assert_eq!(one.len(), 1);
2185 assert!(one.iter().all(|r| full.contains(r)));
2187
2188 let (_, all) = eval_sparql(&rete, &format!("{q} LIMIT 100")).unwrap();
2190 assert_eq!(all.len(), 3);
2191 let (_, off) = eval_sparql(&rete, &format!("{q} LIMIT 100 OFFSET 2")).unwrap();
2192 assert_eq!(off.len(), 1);
2193 }
2194
2195 #[test]
2196 fn limit_early_out_filter_over_bgp() {
2197 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2200 let bytes = rete_from(&[
2201 ("<http://ex/a>", "<http://ex/n>", &format!("\"10\"^^{xsd}")),
2202 ("<http://ex/b>", "<http://ex/n>", &format!("\"20\"^^{xsd}")),
2203 ("<http://ex/c>", "<http://ex/n>", &format!("\"30\"^^{xsd}")),
2204 ("<http://ex/d>", "<http://ex/n>", &format!("\"40\"^^{xsd}")),
2205 ]);
2206 let rete = Rete::open(&bytes).unwrap();
2207 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { ?p ex:n ?v FILTER(?v > 15) }";
2208 let (_, full) = eval_sparql(&rete, q).unwrap();
2209 assert_eq!(full.len(), 3); let (_, two) = eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap();
2211 assert_eq!(two.len(), 2);
2212 assert!(two.iter().all(|r| full.contains(r)));
2213 }
2214
2215 #[test]
2216 fn distinct_bgp_fast_matches_general() {
2217 let bytes = rete_from(&[
2220 ("<http://ex/a>", "<http://ex/p>", "<http://ex/x>"),
2221 ("<http://ex/b>", "<http://ex/p>", "<http://ex/x>"),
2222 ("<http://ex/b>", "<http://ex/p>", "<http://ex/y>"),
2223 ("<http://ex/c>", "<http://ex/p>", "<http://ex/z>"),
2224 ]);
2225 let rete = Rete::open(&bytes).unwrap();
2226 let q = "PREFIX ex: <http://ex/> SELECT DISTINCT ?o WHERE { ?s ex:p ?o }";
2228 let (proj, sols) = eval_sparql(&rete, q).unwrap();
2229 assert_eq!(proj, vec!["o"]);
2230 let mut os: Vec<&str> = sols.iter().map(|b| b["o"].as_str()).collect();
2231 os.sort();
2232 assert_eq!(os, vec!["<http://ex/x>", "<http://ex/y>", "<http://ex/z>"]);
2233 assert_eq!(
2235 eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap().1.len(),
2236 2
2237 );
2238 let q2 = "PREFIX ex: <http://ex/> SELECT DISTINCT ?s ?o WHERE { ?s ex:p ?o }";
2240 assert_eq!(eval_sparql(&rete, q2).unwrap().1.len(), 4);
2241 }
2242
2243 #[test]
2244 fn limit_caps_solutions() {
2245 let bytes = rete_from(&[
2246 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
2247 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
2248 ("<http://ex/c>", "<http://ex/p>", "<http://ex/3>"),
2249 ]);
2250 let rete = Rete::open(&bytes).unwrap();
2251 let (_, sols) =
2252 eval_sparql(&rete, "SELECT ?x WHERE { ?x <http://ex/p> ?y } LIMIT 2").unwrap();
2253 assert_eq!(sols.len(), 2);
2254 }
2255
2256 #[test]
2263 fn string_filters_see_past_an_embedded_quote() {
2264 let bytes = rete_from(&[
2265 (
2266 "<http://ex/a>",
2267 "<http://ex/label>",
2268 "\"plain Quantum here\"",
2269 ),
2270 (
2271 "<http://ex/b>",
2272 "<http://ex/label>",
2273 r#""He said \"Quantum\" loudly"@en"#,
2274 ),
2275 (
2276 "<http://ex/c>",
2277 "<http://ex/label>",
2278 r#""Theory of \"Quantum\" Gases""#,
2279 ),
2280 ("<http://ex/d>", "<http://ex/label>", "\"no match at all\""),
2281 (
2282 "<http://ex/e>",
2283 "<http://ex/label>",
2284 "\"tab\\there\\nline two\"^^<http://www.w3.org/2001/XMLSchema#string>",
2285 ),
2286 ]);
2287 let rete = Rete::open(&bytes).unwrap();
2288 let subjects = |filter: &str| {
2289 let q = format!("SELECT ?s WHERE {{ ?s <http://ex/label> ?l FILTER({filter}) }}");
2290 let (_, sols) = eval_sparql(&rete, &q).unwrap();
2291 let mut out: Vec<String> = sols.iter().map(|b| b["s"].clone()).collect();
2292 out.sort();
2293 out
2294 };
2295 let (a, b, c, e) = (
2296 "<http://ex/a>",
2297 "<http://ex/b>",
2298 "<http://ex/c>",
2299 "<http://ex/e>",
2300 );
2301 assert_eq!(subjects(r#"CONTAINS(?l, "Quantum")"#), vec![a, b, c]);
2302 assert_eq!(subjects(r#"CONTAINS(?l, "Gases")"#), vec![c]);
2303 assert_eq!(subjects(r#"STRENDS(?l, "loudly")"#), vec![b]);
2304 assert_eq!(subjects(r#"STRSTARTS(?l, "Theory of \"Q")"#), vec![c]);
2305 assert_eq!(subjects(r#"REGEX(?l, "gases$", "i")"#), vec![c]);
2306 assert_eq!(subjects(r#"REGEX(?l, "^line two$", "m")"#), vec![e]);
2307 assert_eq!(subjects(r#"CONTAINS(?l, "\there")"#), vec![e]);
2308 assert_eq!(subjects(r#"LANGMATCHES(LANG(?l), "en")"#), vec![b]);
2309 assert_eq!(subjects(r#"STRAFTER(?l, "Quantum") = "\" Gases""#), vec![c]);
2310 assert_eq!(
2311 subjects(r#"STRBEFORE(?l, " loudly") = "He said \"Quantum\""@en"#),
2312 vec![b]
2313 );
2314 assert_eq!(subjects(r#"STRLEN(?l) = 25"#), vec![c]);
2315 assert_eq!(subjects(r#"STRLEN(?l) = 24"#), vec![b]);
2316 }
2317}