1use spargebra::Query;
13
14use crate::bgp::{Binding, PatternTerm, TriplePattern};
15use crate::file::Rete;
16
17mod aggregate;
18mod diag;
19mod distinct;
20mod distinct_union;
21mod eval;
22mod expr;
23mod lower;
24mod partition;
25mod path;
26mod ql;
27mod sparql12;
28mod value;
29
30pub use diag::{warnings_json, QueryWarning, WarningSeverity};
31use eval::{ask_solution, instantiate, raw_solutions, run_select, run_select_communities};
32use lower::{lower_pattern, lower_select, parse_query};
33pub use lower::{parse_select, query_predicates};
34pub use partition::PartitionPlan;
35pub(crate) use eval::eval_plan_in;
38
39#[derive(Debug, thiserror::Error)]
40#[non_exhaustive]
41pub enum SparqlError {
42 #[error("parse error: {0}")]
43 Parse(String),
44 #[error("unsupported query feature: {0}")]
45 Unsupported(&'static str),
46 #[error("unsupported query feature: function <{0}> is not implemented")]
49 UnsupportedFunction(String),
50 #[error("SERVICE federation: {0}")]
54 Service(String),
55 #[error("cannot split this query {0}")]
59 Partition(String),
60}
61
62#[derive(Debug, Clone)]
64pub struct Select {
65 pub project: Vec<String>,
67 pub distinct: bool,
69 pub offset: usize,
71 pub limit: Option<usize>,
73 pub group: Option<GroupSpec>,
75 pub extends: Vec<(String, FExpr)>,
78 pub order: Vec<(FExpr, bool)>,
80 pub having: Vec<FExpr>,
82 pub from: Vec<String>,
85 pub from_named: Option<Vec<String>>,
87 pub union_default: bool,
95 pub plan: Plan,
97 pub star_counter: usize,
102}
103
104impl Default for Select {
105 fn default() -> Self {
106 Select {
107 project: Vec::new(),
108 distinct: false,
109 offset: 0,
110 limit: None,
111 group: None,
112 extends: Vec::new(),
113 order: Vec::new(),
114 having: Vec::new(),
115 from: Vec::new(),
116 from_named: None,
117 union_default: false,
118 plan: Plan::Bgp(Vec::new()),
119 star_counter: 0,
120 }
121 }
122}
123
124#[derive(Debug, Clone)]
126pub struct GroupSpec {
127 pub by: Vec<String>,
129 pub aggs: Vec<(String, Agg)>,
131 pub pre: Vec<(String, FExpr)>,
136}
137
138#[derive(Debug, Clone)]
140pub enum Agg {
141 CountStar {
143 distinct: bool,
144 },
145 Count(String, bool),
147 Sum(String),
148 Avg(String),
149 Min(String),
150 Max(String),
151 Sample(String),
153 GroupConcat(String, String, bool),
156}
157
158#[derive(Debug, Clone)]
160#[must_use]
161pub enum Plan {
162 Bgp(Vec<TriplePattern>),
164 Join(Box<Plan>, Box<Plan>),
166 Union(Box<Plan>, Box<Plan>),
168 LeftJoin(Box<Plan>, Box<Plan>, Option<FExpr>),
171 Filter(FExpr, Box<Plan>),
173 Extend(String, FExpr, Box<Plan>),
178 Path(PatternTerm, PathAst, PatternTerm),
180 Values(Vec<String>, Vec<Vec<Option<String>>>),
183 Minus(Box<Plan>, Box<Plan>),
186 Graph(GraphTarget, Box<Plan>),
188 DistinctUnion {
201 vars: Vec<String>,
202 sources: Vec<TriplePattern>,
203 },
204 Subquery(Box<Select>),
208 Service {
216 silent: bool,
217 endpoint: String,
219 vars: Vec<String>,
222 query: String,
224 },
225}
226
227#[derive(Debug, Clone)]
229pub enum GraphTarget {
230 Named(String),
232 Var(String),
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub enum Rep {
239 One,
241 OneOrMore,
243 ZeroOrMore,
245 ZeroOrOne,
247}
248
249#[derive(Debug, Clone)]
252pub enum PathAst {
253 Pred(String, bool),
255 Rep(Box<PathAst>, Rep),
257 Seq(Box<PathAst>, Box<PathAst>),
259 Alt(Box<PathAst>, Box<PathAst>),
261 NegatedSet(Vec<String>, bool),
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum Op {
270 Eq,
271 Ne,
272 Lt,
273 Le,
274 Gt,
275 Ge,
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub enum ArithOp {
281 Add,
282 Sub,
283 Mul,
284 Div,
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290pub enum Builtin {
291 Str,
292 StrLen,
293 UCase,
294 LCase,
295 Abs,
296 Ceil,
297 Floor,
298 Round,
299 Concat,
300 SubStr,
301 StrBefore,
302 StrAfter,
303 Contains,
304 StrStarts,
305 StrEnds,
306 IsIri,
307 IsBlank,
308 IsLiteral,
309 IsNumeric,
310 Datatype,
311 Lang,
312 Regex,
313 LangMatches,
314 StrDt,
315 StrLang,
316 Iri,
317 EncodeForUri,
318 Replace,
319 Md5,
320 Sha1,
321 Sha256,
322 Sha384,
323 Sha512,
324 Year,
325 Month,
326 Day,
327 Hours,
328 Minutes,
329 Seconds,
330 Timezone,
331 Tz,
332 CastInteger,
333 CastDecimal,
334 CastFloat,
335 CastDouble,
336 CastBoolean,
337 CastString,
338 Rand,
339 Uuid,
340 StrUuid,
341 BNode,
342 GeoSfContains,
345 GeoSfWithin,
346 GeoSfIntersects,
347 GeoSfDisjoint,
348 GeoSfEquals,
349 GeoDistance,
350 GeoEnvelope,
351 Geo3Distance,
353 Geo3Contains,
354 Geo3Within,
355 Geo3Adjacent,
356 TripleTerm,
359 IsTriple,
361 Subject,
363 Predicate,
364 Object,
365}
366
367#[derive(Debug, Clone)]
369pub enum FExpr {
370 Var(String),
371 Const(String),
373 Arith(ArithOp, Box<FExpr>, Box<FExpr>),
375 Func(Builtin, Vec<FExpr>),
377 Coalesce(Vec<FExpr>),
379 If(Box<FExpr>, Box<FExpr>, Box<FExpr>),
381 In(Box<FExpr>, Vec<FExpr>),
383 SameTerm(Box<FExpr>, Box<FExpr>),
385 Compare(Op, Box<FExpr>, Box<FExpr>),
386 And(Box<FExpr>, Box<FExpr>),
387 Or(Box<FExpr>, Box<FExpr>),
388 Not(Box<FExpr>),
389 Bound(String),
390 Exists(Box<Plan>),
393}
394
395type ExistsCache = std::collections::HashMap<*const Plan, ExistsEntry>;
398
399struct ExistsEntry {
403 sols: Vec<crate::row::Row>,
404 probe: Option<ExistsProbe>,
405}
406
407struct ExistsProbe {
411 svars: Vec<usize>,
413 jvars: Vec<usize>,
415 keys: std::collections::HashSet<Vec<crate::row::Val>>,
417 partial: Vec<crate::row::Row>,
419}
420
421fn build_exists_probe(b: &crate::row::Row, sols: &[crate::row::Row]) -> ExistsProbe {
424 let mask = crate::row::bound_mask(sols, b.len());
425 let svars: Vec<usize> = (0..b.len()).filter(|&i| mask[i]).collect();
426 let jvars: Vec<usize> = svars.iter().copied().filter(|&i| b[i].is_some()).collect();
427 let mut keys = std::collections::HashSet::new();
428 let mut partial = Vec::new();
429 for s in sols {
430 match jvars
431 .iter()
432 .map(|&i| s[i].clone())
433 .collect::<Option<Vec<crate::row::Val>>>()
434 {
435 Some(k) => {
436 keys.insert(k);
437 }
438 None => partial.push(s.clone()),
439 }
440 }
441 ExistsProbe {
442 svars,
443 jvars,
444 keys,
445 partial,
446 }
447}
448
449fn exists_matches(b: &crate::row::Row, entry: &ExistsEntry) -> bool {
453 let probe = entry.probe.as_ref().unwrap();
454 let bj: Vec<usize> = probe
455 .svars
456 .iter()
457 .copied()
458 .filter(|&i| b[i].is_some())
459 .collect();
460 if bj == probe.jvars {
461 let k: Vec<crate::row::Val> = probe.jvars.iter().map(|&i| b[i].clone().unwrap()).collect();
462 probe.keys.contains(&k)
463 || probe
464 .partial
465 .iter()
466 .any(|s| crate::row::compatible_rows(b, s))
467 } else {
468 entry.sols.iter().any(|s| crate::row::compatible_rows(b, s))
469 }
470}
471
472fn lexical(token: &str) -> String {
479 crate::terms::literal_lexical(token).unwrap_or_else(|| token.to_string())
480}
481
482pub(crate) fn term_number(s: &str) -> Option<f64> {
487 value::numeric(s)
488}
489
490use crate::terms::as_number;
491
492#[derive(Debug, Clone)]
494#[must_use]
495#[non_exhaustive]
496pub enum QueryOutput {
497 Select(Vec<String>, Vec<Binding>),
499 Ask(bool),
501 Construct(Vec<(String, String, String)>),
503}
504
505#[derive(Debug, Clone, PartialEq, Eq)]
508#[must_use]
509#[non_exhaustive]
510pub enum SummaryQueryShape {
511 PredicateCount { predicate: String, variable: String },
513 TripleCount { variable: String },
515 PredicateTotals {
517 predicate_variable: String,
518 count_variable: String,
519 },
520 PredicateList { variable: String },
522 PredicateDistinctCount { variable: String },
524 TripleExists,
526 PredicateExists { predicate: String },
528}
529
530#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct RoutedTriplePattern {
535 pub subject: Option<String>,
536 pub predicate: Option<String>,
537 pub object: Option<String>,
538}
539
540pub fn routed_triple_pattern(query: &str) -> Result<Option<RoutedTriplePattern>, SparqlError> {
545 let parsed = parse_query(query)?;
546 let sel = match parsed {
547 Query::Select {
548 pattern, dataset, ..
549 } => lower_select(&pattern, &dataset)?,
550 Query::Ask { pattern, .. } => lower_pattern(&pattern)?,
551 Query::Construct { pattern, .. } => lower_pattern(&pattern)?,
552 Query::Describe { pattern, .. } => lower_pattern(&pattern)?,
553 };
554 if !sel.from.is_empty() || sel.from_named.is_some() {
555 return Ok(None);
556 }
557 let Plan::Bgp(patterns) = sel.plan else {
558 return Ok(None);
559 };
560 let [tp] = patterns.as_slice() else {
561 return Ok(None);
562 };
563 Ok(Some(RoutedTriplePattern {
564 subject: term_const(&tp.s),
565 predicate: term_const(&tp.p),
566 object: term_const(&tp.o),
567 }))
568}
569
570fn term_const(term: &PatternTerm) -> Option<String> {
571 match term {
572 PatternTerm::Const(t) => Some(t.clone()),
573 PatternTerm::Var(_) => None,
574 }
575}
576
577pub fn summary_query_shape(query: &str) -> Result<Option<SummaryQueryShape>, SparqlError> {
584 let parsed = parse_query(query)?;
585 match parsed {
586 Query::Select {
587 pattern, dataset, ..
588 } => {
589 let sel = lower_select(&pattern, &dataset)?;
590 if !sel.from.is_empty()
591 || sel.from_named.is_some()
592 || sel.offset != 0
593 || sel.limit.is_some()
594 || !sel.order.is_empty()
595 || !sel.having.is_empty()
596 {
597 return Ok(None);
598 }
599 if sel.distinct {
600 if sel.group.is_some() || !sel.extends.is_empty() {
601 return Ok(None);
602 }
603 let [projected] = sel.project.as_slice() else {
604 return Ok(None);
605 };
606 let Some(SummaryPatternShape::AnyPredicate { variable }) =
607 single_summary_pattern(&sel.plan)
608 else {
609 return Ok(None);
610 };
611 return if projected == &variable {
612 Ok(Some(SummaryQueryShape::PredicateList { variable }))
613 } else {
614 Ok(None)
615 };
616 }
617 let Some(group) = &sel.group else {
618 return Ok(None);
619 };
620 if group.aggs.len() != 1 {
621 return Ok(None);
622 }
623 match group.by.as_slice() {
624 [] => match &group.aggs[0].1 {
625 Agg::CountStar { distinct: false } => {
626 let Some(variable) = public_aggregate_variable(&sel, &group.aggs[0].0)
627 else {
628 return Ok(None);
629 };
630 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
631 SummaryPatternShape::Predicate(predicate) => {
632 SummaryQueryShape::PredicateCount {
633 predicate,
634 variable,
635 }
636 }
637 SummaryPatternShape::AnyPredicate { .. } => {
638 SummaryQueryShape::TripleCount { variable }
639 }
640 }))
641 }
642 Agg::Count(counted, true) => {
643 let Some(public_variable) =
644 public_aggregate_variable(&sel, &group.aggs[0].0)
645 else {
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 != counted {
654 return Ok(None);
655 }
656 Ok(Some(SummaryQueryShape::PredicateDistinctCount {
657 variable: public_variable,
658 }))
659 }
660 _ => Ok(None),
661 },
662 [group_var] => {
663 if !matches!(group.aggs[0].1, Agg::CountStar { distinct: false }) {
664 return Ok(None);
665 }
666 let Some(SummaryPatternShape::AnyPredicate { variable }) =
667 single_summary_pattern(&sel.plan)
668 else {
669 return Ok(None);
670 };
671 if &variable != group_var {
672 return Ok(None);
673 }
674 let Some(count_variable) =
675 public_group_aggregate_variable(&sel, &group.aggs[0].0, group_var)
676 else {
677 return Ok(None);
678 };
679 Ok(Some(SummaryQueryShape::PredicateTotals {
680 predicate_variable: group_var.clone(),
681 count_variable,
682 }))
683 }
684 _ => Ok(None),
685 }
686 }
687 Query::Ask { pattern, .. } => {
688 let sel = lower_pattern(&pattern)?;
689 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
690 SummaryPatternShape::Predicate(predicate) => {
691 SummaryQueryShape::PredicateExists { predicate }
692 }
693 SummaryPatternShape::AnyPredicate { .. } => SummaryQueryShape::TripleExists,
694 }))
695 }
696 Query::Construct { .. } | Query::Describe { .. } => Ok(None),
697 }
698}
699
700enum SummaryPatternShape {
701 Predicate(String),
702 AnyPredicate { variable: String },
703}
704
705fn single_summary_pattern(plan: &Plan) -> Option<SummaryPatternShape> {
706 let Plan::Bgp(patterns) = plan else {
707 return None;
708 };
709 let [tp] = patterns.as_slice() else {
710 return None;
711 };
712 let (PatternTerm::Var(s), PatternTerm::Var(o)) = (&tp.s, &tp.o) else {
713 return None;
714 };
715 if s == o {
716 return None;
717 }
718 match &tp.p {
719 PatternTerm::Const(p) => Some(SummaryPatternShape::Predicate(p.clone())),
720 PatternTerm::Var(p) if p != s && p != o => Some(SummaryPatternShape::AnyPredicate {
721 variable: p.clone(),
722 }),
723 _ => None,
724 }
725}
726
727fn public_aggregate_variable(sel: &Select, aggregate_var: &str) -> Option<String> {
728 let [projected] = sel.project.as_slice() else {
729 return None;
730 };
731 if projected == aggregate_var {
732 return Some(projected.clone());
733 }
734 sel.extends.iter().find_map(|(var, expr)| match expr {
735 FExpr::Var(source) if var == projected && source == aggregate_var => Some(var.clone()),
736 _ => None,
737 })
738}
739
740fn public_group_aggregate_variable(
741 sel: &Select,
742 aggregate_var: &str,
743 group_var: &str,
744) -> Option<String> {
745 let [projected_group, projected_aggregate] = sel.project.as_slice() else {
746 return None;
747 };
748 if projected_group != group_var {
749 return None;
750 }
751 if projected_aggregate == aggregate_var {
752 return Some(projected_aggregate.clone());
753 }
754 sel.extends.iter().find_map(|(var, expr)| match expr {
755 FExpr::Var(source) if var == projected_aggregate && source == aggregate_var => {
756 Some(var.clone())
757 }
758 _ => None,
759 })
760}
761
762#[derive(Debug, Clone, Copy)]
765pub struct CommunityPartial {
766 pub community: usize,
767 pub subjects: usize,
768 pub rows: usize,
769}
770
771pub type CommunitySelect = (Vec<String>, Vec<Binding>, Vec<CommunityPartial>);
774
775pub fn eval_select_communities(
786 rete: &Rete,
787 query: &str,
788 round: Option<usize>,
789) -> Result<CommunitySelect, SparqlError> {
790 let parsed = parse_query(query)?;
791 let out = match parsed {
792 Query::Select {
793 pattern, dataset, ..
794 } => run_select_communities(rete, &lower_select(&pattern, &dataset)?, round),
795 _ => Err(SparqlError::Unsupported(
796 "community-split evaluation supports SELECT queries only",
797 )),
798 };
799 match rete.take_service_error() {
802 Some(e) => Err(SparqlError::Service(e)),
803 None => out,
804 }
805}
806
807pub fn eval_query(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
809 eval_query_with(rete, query, QueryOpts::default())
810}
811
812pub fn eval_query_reasoned(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
817 eval_query_with(
818 rete,
819 query,
820 QueryOpts {
821 reason: true,
822 ..QueryOpts::default()
823 },
824 )
825}
826
827#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
831pub struct QueryOpts {
832 pub reason: bool,
834 pub union_default_graph: bool,
840}
841
842pub fn eval_query_with(
845 rete: &Rete,
846 query: &str,
847 opts: QueryOpts,
848) -> Result<QueryOutput, SparqlError> {
849 diag::begin();
853 let out = eval_query_inner(rete, query, opts);
854 match rete.take_service_error() {
858 Some(e) => Err(SparqlError::Service(e)),
859 None => out,
860 }
861}
862
863pub fn eval_query_with_warnings(
880 rete: &Rete,
881 query: &str,
882 opts: QueryOpts,
883) -> Result<(QueryOutput, Vec<QueryWarning>), SparqlError> {
884 let out = eval_query_with(rete, query, opts)?;
885 let warnings = diag::finish(&out);
886 Ok((out, warnings))
887}
888
889pub fn partition_plan(rete: &Rete, query: &str, var: &str) -> Result<PartitionPlan, SparqlError> {
905 partition::plan(rete, query, var)
906}
907
908pub fn eval_query_partitioned(
918 rete: &Rete,
919 query: &str,
920 opts: QueryOpts,
921 var: &str,
922 keys: &[String],
923) -> Result<(QueryOutput, Vec<QueryWarning>), SparqlError> {
924 diag::begin();
925 let out = eval_partition_inner(rete, query, opts, partition::var_name(var), keys);
926 let out = match rete.take_service_error() {
927 Some(e) => Err(SparqlError::Service(e)),
928 None => out,
929 }?;
930 let warnings = diag::finish(&out);
931 Ok((out, warnings))
932}
933
934fn eval_partition_inner(
935 rete: &Rete,
936 query: &str,
937 opts: QueryOpts,
938 var: &str,
939 keys: &[String],
940) -> Result<QueryOutput, SparqlError> {
941 let parsed = parse_query(query)?;
942 partition::check(&parsed, var)?;
943 let chunk = |sel: Select| -> Select {
946 let mut sel = maybe_reason(rete, apply_opts(sel, opts), opts.reason);
947 sel.offset = 0;
948 sel.limit = None;
949 sel.plan = partition::seed(sel.plan, var, keys);
950 sel
951 };
952 match parsed {
953 Query::Select {
954 pattern, dataset, ..
955 } => {
956 let (vars, rows) = run_select(rete, &chunk(lower_select(&pattern, &dataset)?));
957 Ok(QueryOutput::Select(vars, rows))
958 }
959 Query::Construct {
960 template, pattern, ..
961 } => {
962 let sel = chunk(lower_pattern(&pattern)?);
963 let (ctx, sols) = raw_solutions(rete, &sel);
964 Ok(QueryOutput::Construct(instantiate(&ctx, &template, &sols)))
965 }
966 _ => Err(SparqlError::Unsupported(
968 "only SELECT and CONSTRUCT can be split",
969 )),
970 }
971}
972
973fn maybe_reason(rete: &Rete, mut sel: Select, reason: bool) -> Select {
976 if reason {
977 let projected = sel.project.clone();
978 sel.plan = ql::reason_rewrite(sel.plan, rete, &projected);
979 }
980 sel
981}
982
983fn apply_opts(mut sel: Select, opts: QueryOpts) -> Select {
988 sel.union_default = opts.union_default_graph;
989 sel
990}
991
992fn eval_query_inner(rete: &Rete, query: &str, opts: QueryOpts) -> Result<QueryOutput, SparqlError> {
993 let reason = opts.reason;
994 let parsed = parse_query(query)?;
995 match parsed {
996 Query::Select {
997 pattern, dataset, ..
998 } => {
999 let (vars, rows) = run_select(
1000 rete,
1001 &maybe_reason(
1002 rete,
1003 apply_opts(lower_select(&pattern, &dataset)?, opts),
1004 reason,
1005 ),
1006 );
1007 Ok(QueryOutput::Select(vars, rows))
1008 }
1009 Query::Ask { pattern, .. } => {
1010 let sel = maybe_reason(rete, apply_opts(lower_pattern(&pattern)?, opts), reason);
1011 Ok(QueryOutput::Ask(ask_solution(rete, &sel)))
1012 }
1013 Query::Construct {
1014 template, pattern, ..
1015 } => {
1016 let sel = maybe_reason(rete, apply_opts(lower_pattern(&pattern)?, opts), reason);
1017 let (ctx, sols) = raw_solutions(rete, &sel);
1018 Ok(QueryOutput::Construct(instantiate(&ctx, &template, &sols)))
1019 }
1020 Query::Describe {
1021 pattern, dataset, ..
1022 } => {
1023 let sel = maybe_reason(
1026 rete,
1027 apply_opts(lower_select(&pattern, &dataset)?, opts),
1028 reason,
1029 );
1030 let (ctx, rows) = raw_solutions(rete, &sel);
1031 let mut resources = std::collections::BTreeSet::new();
1032 for row in &rows {
1033 if sel.project.is_empty() {
1034 for val in row.iter().flatten() {
1035 if let Some(t) = ctx.resolver.str_of(val) {
1036 resources.insert(t.to_string());
1037 }
1038 }
1039 } else {
1040 for v in &sel.project {
1041 if let Some(val) = ctx.slots.slot(v).and_then(|s| row[s].as_ref()) {
1042 if let Some(t) = ctx.resolver.str_of(val) {
1043 resources.insert(t.to_string());
1044 }
1045 }
1046 }
1047 }
1048 }
1049 let mut graphs: Vec<Option<&str>> = Vec::new();
1054 if !sel.from.is_empty() {
1055 graphs.extend(sel.from.iter().map(|g| Some(g.as_str())));
1056 } else {
1057 graphs.push(None);
1058 if sel.union_default {
1059 graphs.extend(rete.graph_names().into_iter().map(Some));
1060 }
1061 }
1062 let mut triples = std::collections::BTreeSet::new();
1063 for r in &resources {
1064 for g in &graphs {
1065 for t in rete.query_in_graph(*g, Some(r), None, None) {
1066 triples.insert(t);
1067 }
1068 }
1069 }
1070 Ok(QueryOutput::Construct(triples.into_iter().collect()))
1071 }
1072 }
1073}
1074
1075pub fn eval_sparql(rete: &Rete, query: &str) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
1079 eval_sparql_opts(rete, query, false)
1080}
1081
1082pub fn eval_sparql_reasoned(
1084 rete: &Rete,
1085 query: &str,
1086) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
1087 eval_sparql_opts(rete, query, true)
1088}
1089
1090fn eval_sparql_opts(
1091 rete: &Rete,
1092 query: &str,
1093 reason: bool,
1094) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
1095 let sel = maybe_reason(rete, parse_select(query)?, reason);
1096 let out = run_select(rete, &sel);
1097 match rete.take_service_error() {
1099 Some(e) => Err(SparqlError::Service(e)),
1100 None => Ok(out),
1101 }
1102}
1103
1104pub(crate) fn fmt_num_typed(x: f64) -> String {
1110 if x.fract() == 0.0 {
1111 format!(
1112 "\"{}\"^^<http://www.w3.org/2001/XMLSchema#integer>",
1113 x as i64
1114 )
1115 } else {
1116 let cleaned: f64 = format!("{x:.14e}").parse().unwrap_or(x);
1120 format!("\"{cleaned}\"^^<http://www.w3.org/2001/XMLSchema#decimal>")
1121 }
1122}
1123
1124fn reverse(ast: PathAst) -> PathAst {
1126 match ast {
1127 PathAst::Pred(p, r) => PathAst::Pred(p, !r),
1128 PathAst::Rep(inner, rep) => PathAst::Rep(Box::new(reverse(*inner)), rep),
1129 PathAst::Seq(a, b) => PathAst::Seq(Box::new(reverse(*b)), Box::new(reverse(*a))),
1130 PathAst::Alt(a, b) => PathAst::Alt(Box::new(reverse(*a)), Box::new(reverse(*b))),
1131 PathAst::NegatedSet(s, r) => PathAst::NegatedSet(s, !r),
1132 }
1133}
1134
1135#[cfg(test)]
1136mod tests {
1137 use super::*;
1138 use crate::dictionary::DictionaryBuilder;
1139 use crate::index::GraphIndexBuilder;
1140 use crate::write_file;
1141
1142 fn rete_from(triples: &[(&str, &str, &str)]) -> Vec<u8> {
1143 let mut db = DictionaryBuilder::new();
1144 for (s, p, o) in triples {
1145 db.observe(s, p, o);
1146 }
1147 let dict = db.build();
1148 let mut ib = GraphIndexBuilder::new();
1149 for (s, p, o) in triples {
1150 ib.push(dict.encode(s, p, o).unwrap());
1151 }
1152 write_file(&dict, &ib.build(), false, &[], 0)
1153 }
1154
1155 #[test]
1156 fn parses_select_with_two_patterns() {
1157 let q = r#"
1158 PREFIX ex: <http://ex/>
1159 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }
1160 "#;
1161 let sel = parse_select(q).unwrap();
1162 assert_eq!(sel.project, vec!["x", "z"]);
1163 match &sel.plan {
1164 Plan::Bgp(p) => assert_eq!(p.len(), 2),
1165 other => panic!("expected a BGP plan, got {other:?}"),
1166 }
1167 }
1168
1169 #[test]
1170 fn evaluates_two_hop_select() {
1171 let bytes = rete_from(&[
1172 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1173 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1174 ]);
1175 let rete = Rete::open(&bytes).unwrap();
1176 let q = r#"PREFIX ex: <http://ex/>
1177 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }"#;
1178 let (proj, sols) = eval_sparql(&rete, q).unwrap();
1179 assert_eq!(proj, vec!["x", "z"]);
1180 assert_eq!(sols.len(), 1);
1181 assert_eq!(sols[0]["x"], "<http://ex/Alice>");
1182 assert_eq!(sols[0]["z"], "<http://ex/Carol>");
1183 }
1184
1185 #[test]
1186 fn union_returns_both_sides() {
1187 let bytes = rete_from(&[
1188 ("<http://ex/Alice>", "<http://ex/likes>", "<http://ex/Tea>"),
1189 ("<http://ex/Bob>", "<http://ex/hates>", "<http://ex/Tea>"),
1190 ]);
1191 let rete = Rete::open(&bytes).unwrap();
1192 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { \
1193 { ?p ex:likes ex:Tea } UNION { ?p ex:hates ex:Tea } }";
1194 let (_, sols) = eval_sparql(&rete, q).unwrap();
1195 let mut who: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
1196 who.sort();
1197 assert_eq!(who, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1198 }
1199
1200 #[test]
1201 fn optional_keeps_left_when_right_absent() {
1202 let bytes = rete_from(&[
1204 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1205 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1206 ("<http://ex/Alice>", "<http://ex/email>", "\"a@ex\""),
1207 ]);
1208 let rete = Rete::open(&bytes).unwrap();
1209 let q = "PREFIX ex: <http://ex/> SELECT ?p ?e WHERE { \
1210 ?p ex:name ?n . OPTIONAL { ?p ex:email ?e } }";
1211 let (_, sols) = eval_sparql(&rete, q).unwrap();
1212 assert_eq!(sols.len(), 2, "both people present");
1213 let alice = sols.iter().find(|b| b["p"] == "<http://ex/Alice>").unwrap();
1214 assert_eq!(alice["e"], "\"a@ex\"");
1215 let bob = sols.iter().find(|b| b["p"] == "<http://ex/Bob>").unwrap();
1216 assert!(!bob.contains_key("e"), "Bob has no email binding");
1217 }
1218
1219 #[test]
1220 fn numeric_filter_on_typed_literal() {
1221 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
1223 let bytes = rete_from(&[
1224 (
1225 "<http://ex/Alice>",
1226 "<http://ex/age>",
1227 &format!("\"30\"^^{xsd}"),
1228 ),
1229 (
1230 "<http://ex/Bob>",
1231 "<http://ex/age>",
1232 &format!("\"25\"^^{xsd}"),
1233 ),
1234 ]);
1235 let rete = Rete::open(&bytes).unwrap();
1236 let q = "PREFIX ex: <http://ex/> \
1237 SELECT ?p WHERE { ?p ex:age ?age . FILTER(?age > 27) }";
1238 let (_, sols) = eval_sparql(&rete, q).unwrap();
1239 assert_eq!(sols.len(), 1);
1240 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1241 }
1242
1243 #[test]
1244 fn filter_equality_and_boolean_logic() {
1245 let bytes = rete_from(&[
1246 ("<http://ex/Alice>", "<http://ex/city>", "<http://ex/NYC>"),
1247 ("<http://ex/Bob>", "<http://ex/city>", "<http://ex/LA>"),
1248 ("<http://ex/Carol>", "<http://ex/city>", "<http://ex/NYC>"),
1249 ]);
1250 let rete = Rete::open(&bytes).unwrap();
1251 let q = "PREFIX ex: <http://ex/> \
1252 SELECT ?p WHERE { ?p ex:city ?c . FILTER(?c = ex:NYC && ?p != ex:Carol) }";
1253 let (_, sols) = eval_sparql(&rete, q).unwrap();
1254 assert_eq!(sols.len(), 1);
1255 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1256 }
1257
1258 #[test]
1259 fn distinct_collapses_duplicate_projections() {
1260 let bytes = rete_from(&[
1262 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1263 (
1264 "<http://ex/Alice>",
1265 "<http://ex/knows>",
1266 "<http://ex/Carol>",
1267 ),
1268 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1269 ("<http://ex/Carol>", "<http://ex/knows>", "<http://ex/Dave>"),
1270 ]);
1271 let rete = Rete::open(&bytes).unwrap();
1272 let base = "PREFIX ex: <http://ex/> SELECT {} ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1273
1274 let (_, non_distinct) = eval_sparql(&rete, &base.replace("{}", "")).unwrap();
1275 assert_eq!(non_distinct.len(), 2); let (proj, distinct) = eval_sparql(&rete, &base.replace("{}", "DISTINCT")).unwrap();
1278 assert_eq!(proj, vec!["z"]);
1279 assert_eq!(distinct.len(), 1);
1280 assert_eq!(distinct[0]["z"], "<http://ex/Dave>");
1281 assert!(!distinct[0].contains_key("x"));
1283 }
1284
1285 #[test]
1286 fn transitive_path_reachability() {
1287 let bytes = rete_from(&[
1289 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
1290 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
1291 ("<http://ex/C>", "<http://ex/k>", "<http://ex/D>"),
1292 ]);
1293 let rete = Rete::open(&bytes).unwrap();
1294
1295 let q = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k+ ?y }";
1297 let (_, sols) = eval_sparql(&rete, q).unwrap();
1298 let mut ys: Vec<&str> = sols.iter().map(|b| b["y"].as_str()).collect();
1299 ys.sort();
1300 assert_eq!(ys, vec!["<http://ex/B>", "<http://ex/C>", "<http://ex/D>"]);
1301
1302 let q0 = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k* ?y }";
1304 let (_, s0) = eval_sparql(&rete, q0).unwrap();
1305 assert!(s0.iter().any(|b| b["y"] == "<http://ex/A>"));
1306 assert_eq!(s0.len(), 4); }
1308
1309 #[test]
1310 fn sequence_and_alternative_paths() {
1311 let bytes = rete_from(&[
1313 ("<http://ex/Alice>", "<http://ex/parent>", "<http://ex/Bob>"),
1314 ("<http://ex/Bob>", "<http://ex/parent>", "<http://ex/Carol>"),
1315 (
1316 "<http://ex/Alice>",
1317 "<http://ex/stepparent>",
1318 "<http://ex/Dave>",
1319 ),
1320 ]);
1321 let rete = Rete::open(&bytes).unwrap();
1322
1323 let q = "PREFIX ex: <http://ex/> SELECT ?g WHERE { ex:Alice ex:parent/ex:parent ?g }";
1325 let (_, sols) = eval_sparql(&rete, q).unwrap();
1326 assert_eq!(sols.len(), 1);
1327 assert_eq!(sols[0]["g"], "<http://ex/Carol>");
1328
1329 let qa = "PREFIX ex: <http://ex/> \
1331 SELECT ?p WHERE { ex:Alice ex:parent|ex:stepparent ?p }";
1332 let (_, sa) = eval_sparql(&rete, qa).unwrap();
1333 let mut ps: Vec<&str> = sa.iter().map(|b| b["p"].as_str()).collect();
1334 ps.sort();
1335 assert_eq!(ps, vec!["<http://ex/Bob>", "<http://ex/Dave>"]);
1336 }
1337
1338 #[test]
1339 fn group_concat_aggregate() {
1340 let bytes = rete_from(&[
1341 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1342 (
1343 "<http://ex/Alice>",
1344 "<http://ex/knows>",
1345 "<http://ex/Carol>",
1346 ),
1347 ]);
1348 let rete = Rete::open(&bytes).unwrap();
1349 let q = "PREFIX ex: <http://ex/> \
1350 SELECT (GROUP_CONCAT(?f; SEPARATOR=\"|\") AS ?fs) WHERE { ex:Alice ex:knows ?f }";
1351 let (_, sols) = eval_sparql(&rete, q).unwrap();
1352 assert_eq!(sols.len(), 1);
1353 let fs = sols[0]["fs"].trim_matches('"');
1355 let mut parts: Vec<&str> = fs.split('|').collect();
1356 parts.sort();
1357 assert_eq!(parts, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1358 }
1359
1360 #[test]
1361 fn group_by_having() {
1362 let bytes = rete_from(&[
1364 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1365 (
1366 "<http://ex/Alice>",
1367 "<http://ex/knows>",
1368 "<http://ex/Carol>",
1369 ),
1370 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1371 ]);
1372 let rete = Rete::open(&bytes).unwrap();
1373 let q = "PREFIX ex: <http://ex/> SELECT ?p (COUNT(?f) AS ?n) \
1375 WHERE { ?p ex:knows ?f } GROUP BY ?p HAVING (COUNT(?f) > 1)";
1376 let (_, sols) = eval_sparql(&rete, q).unwrap();
1377 assert_eq!(sols.len(), 1);
1378 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1379 assert_eq!(
1380 sols[0]["n"],
1381 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1382 );
1383 }
1384
1385 #[test]
1386 fn count_group_by() {
1387 let bytes = rete_from(&[
1389 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1390 (
1391 "<http://ex/Alice>",
1392 "<http://ex/knows>",
1393 "<http://ex/Carol>",
1394 ),
1395 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1396 ]);
1397 let rete = Rete::open(&bytes).unwrap();
1398 let q = "PREFIX ex: <http://ex/> \
1399 SELECT ?p (COUNT(?f) AS ?n) WHERE { ?p ex:knows ?f } GROUP BY ?p";
1400 let (_, sols) = eval_sparql(&rete, q).unwrap();
1401 let mut counts: Vec<(String, String)> = sols
1402 .iter()
1403 .map(|b| (b["p"].clone(), b["n"].clone()))
1404 .collect();
1405 counts.sort();
1406 assert_eq!(
1407 counts,
1408 vec![
1409 (
1410 "<http://ex/Alice>".into(),
1411 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1412 ),
1413 (
1414 "<http://ex/Bob>".into(),
1415 "\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1416 ),
1417 ]
1418 );
1419 }
1420
1421 #[test]
1422 fn global_count_star() {
1423 let bytes = rete_from(&[
1424 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
1425 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
1426 ]);
1427 let rete = Rete::open(&bytes).unwrap();
1428 let (_, sols) = eval_sparql(&rete, "SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1429 assert_eq!(sols.len(), 1);
1430 assert_eq!(
1431 sols[0]["n"],
1432 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1433 );
1434 }
1435
1436 #[test]
1437 fn summary_query_shape_classifies_only_exact_predicate_totals() {
1438 let count = summary_query_shape(
1439 "PREFIX ex: <http://ex/> SELECT (COUNT(*) AS ?n) WHERE { ?s ex:p ?o }",
1440 )
1441 .unwrap();
1442 assert_eq!(
1443 count,
1444 Some(SummaryQueryShape::PredicateCount {
1445 predicate: "<http://ex/p>".into(),
1446 variable: "n".into(),
1447 })
1448 );
1449
1450 let total = summary_query_shape("SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1451 assert_eq!(
1452 total,
1453 Some(SummaryQueryShape::TripleCount {
1454 variable: "n".into(),
1455 })
1456 );
1457
1458 let by_pred =
1459 summary_query_shape("SELECT ?p (COUNT(*) AS ?n) WHERE { ?s ?p ?o } GROUP BY ?p")
1460 .unwrap();
1461 assert_eq!(
1462 by_pred,
1463 Some(SummaryQueryShape::PredicateTotals {
1464 predicate_variable: "p".into(),
1465 count_variable: "n".into(),
1466 })
1467 );
1468
1469 let predicates = summary_query_shape("SELECT DISTINCT ?p WHERE { ?s ?p ?o }").unwrap();
1470 assert_eq!(
1471 predicates,
1472 Some(SummaryQueryShape::PredicateList {
1473 variable: "p".into(),
1474 })
1475 );
1476
1477 let predicate_count =
1478 summary_query_shape("SELECT (COUNT(DISTINCT ?p) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1479 assert_eq!(
1480 predicate_count,
1481 Some(SummaryQueryShape::PredicateDistinctCount {
1482 variable: "n".into(),
1483 })
1484 );
1485
1486 let ask = summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o }").unwrap();
1487 assert_eq!(
1488 ask,
1489 Some(SummaryQueryShape::PredicateExists {
1490 predicate: "<http://ex/p>".into(),
1491 })
1492 );
1493
1494 let any_ask = summary_query_shape("ASK { ?s ?p ?o }").unwrap();
1495 assert_eq!(any_ask, Some(SummaryQueryShape::TripleExists));
1496
1497 let constrained =
1498 summary_query_shape("PREFIX ex: <http://ex/> ASK { ex:a ex:p ?o }").unwrap();
1499 assert_eq!(constrained, None);
1500
1501 let filtered =
1502 summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o FILTER(?s = ?o) }")
1503 .unwrap();
1504 assert_eq!(filtered, None);
1505 }
1506
1507 #[test]
1508 fn filter_exists_and_not_exists() {
1509 let bytes = rete_from(&[
1511 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1512 (
1513 "<http://ex/Alice>",
1514 "<http://ex/knows>",
1515 "<http://ex/Carol>",
1516 ),
1517 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1518 ]);
1519 let rete = Rete::open(&bytes).unwrap();
1520
1521 let q_exists = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1523 ex:Alice ex:knows ?f . FILTER EXISTS { ?f ex:knows ?x } }";
1524 let (_, e) = eval_sparql(&rete, q_exists).unwrap();
1525 assert_eq!(
1526 e.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1527 vec!["<http://ex/Bob>"]
1528 );
1529
1530 let q_not = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1532 ex:Alice ex:knows ?f . FILTER NOT EXISTS { ?f ex:knows ?x } }";
1533 let (_, n) = eval_sparql(&rete, q_not).unwrap();
1534 assert_eq!(
1535 n.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1536 vec!["<http://ex/Carol>"]
1537 );
1538 }
1539
1540 #[test]
1541 fn ask_repeated_variable_pattern() {
1542 let yes = rete_from(&[
1545 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1546 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Bob>"),
1547 ]);
1548 let rete = Rete::open(&yes).unwrap();
1549 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1550 QueryOutput::Ask(b) => assert!(b, "Bob knows himself"),
1551 other => panic!("expected Ask, got {other:?}"),
1552 }
1553 let no = rete_from(&[("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>")]);
1556 let rete = Rete::open(&no).unwrap();
1557 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1558 QueryOutput::Ask(b) => assert!(!b, "nobody knows themselves"),
1559 other => panic!("expected Ask, got {other:?}"),
1560 }
1561 }
1562
1563 #[test]
1564 fn minus_excludes_compatible() {
1565 let bytes = rete_from(&[
1567 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1568 (
1569 "<http://ex/Alice>",
1570 "<http://ex/knows>",
1571 "<http://ex/Carol>",
1572 ),
1573 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1574 ]);
1575 let rete = Rete::open(&bytes).unwrap();
1576 let q = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1578 ex:Alice ex:knows ?f . MINUS { ?f ex:knows ?x } }";
1579 let (_, sols) = eval_sparql(&rete, q).unwrap();
1580 let fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1581 assert_eq!(fs, vec!["<http://ex/Carol>"]);
1582 }
1583
1584 #[test]
1585 fn filter_exists_disjoint_variable() {
1586 let bytes = rete_from(&[
1591 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1592 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1593 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1594 ]);
1595 let rete = Rete::open(&bytes).unwrap();
1596 let q_none = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1597 ?x ex:a ex:Person FILTER NOT EXISTS { ?y ex:a ex:Drink } }";
1598 assert!(eval_sparql(&rete, q_none).unwrap().1.is_empty());
1599 let q_all = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1600 ?x ex:a ex:Person FILTER EXISTS { ?y ex:a ex:Drink } }";
1601 assert_eq!(eval_sparql(&rete, q_all).unwrap().1.len(), 2);
1602 }
1603
1604 #[test]
1605 fn minus_disjoint_domain_keeps_all() {
1606 let bytes = rete_from(&[
1610 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1611 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1612 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1613 ]);
1614 let rete = Rete::open(&bytes).unwrap();
1615 let q = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1616 ?x ex:a ex:Person MINUS { ?y ex:a ex:Drink } }";
1617 let (_, sols) = eval_sparql(&rete, q).unwrap();
1618 let mut xs: Vec<&str> = sols.iter().map(|b| b["x"].as_str()).collect();
1619 xs.sort();
1620 assert_eq!(xs, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1621 }
1622
1623 #[test]
1624 fn values_pushdown_selects_subset() {
1625 let bytes = rete_from(&[
1628 ("<http://ex/a>", "<http://ex/d>", "<http://ex/Bio>"),
1629 ("<http://ex/b>", "<http://ex/d>", "<http://ex/Phys>"),
1630 ("<http://ex/c>", "<http://ex/d>", "<http://ex/Chem>"),
1631 ]);
1632 let rete = Rete::open(&bytes).unwrap();
1633 let q = "PREFIX ex: <http://ex/> SELECT ?p ?disc WHERE { \
1634 VALUES ?disc { ex:Bio ex:Phys } ?p ex:d ?disc }";
1635 let (_, sols) = eval_sparql(&rete, q).unwrap();
1636 let mut got: Vec<(String, String)> = sols
1637 .iter()
1638 .map(|b| (b["p"].clone(), b["disc"].clone()))
1639 .collect();
1640 got.sort();
1641 assert_eq!(
1642 got,
1643 vec![
1644 ("<http://ex/a>".into(), "<http://ex/Bio>".into()),
1645 ("<http://ex/b>".into(), "<http://ex/Phys>".into()),
1646 ]
1647 );
1648 }
1649
1650 #[test]
1651 fn values_inline_data_joins() {
1652 let bytes = rete_from(&[
1653 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1654 (
1655 "<http://ex/Alice>",
1656 "<http://ex/knows>",
1657 "<http://ex/Carol>",
1658 ),
1659 ("<http://ex/Dave>", "<http://ex/knows>", "<http://ex/Eve>"),
1660 ]);
1661 let rete = Rete::open(&bytes).unwrap();
1662 let q = "PREFIX ex: <http://ex/> \
1664 SELECT ?f WHERE { VALUES ?p { ex:Alice } ?p ex:knows ?f }";
1665 let (_, sols) = eval_sparql(&rete, q).unwrap();
1666 let mut fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1667 fs.sort();
1668 assert_eq!(fs, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1669 }
1670
1671 #[test]
1672 fn graph_queries_over_named_graphs() {
1673 use crate::write_dataset;
1674 let mut db = DictionaryBuilder::new();
1676 let edges = [
1677 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1678 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1679 ];
1680 for (s, p, o) in edges {
1681 db.observe(s, p, o);
1682 }
1683 let dict = db.build();
1684 let mut social = GraphIndexBuilder::new();
1685 social.push(dict.encode(edges[0].0, edges[0].1, edges[0].2).unwrap());
1686 let mut profile = GraphIndexBuilder::new();
1687 profile.push(dict.encode(edges[1].0, edges[1].1, edges[1].2).unwrap());
1688 let named = vec![
1689 ("<http://ex/social>".to_string(), social.build()),
1690 ("<http://ex/profile>".to_string(), profile.build()),
1691 ];
1692 let bytes = write_dataset(
1693 &dict,
1694 &GraphIndexBuilder::new().build(),
1695 &named,
1696 true,
1697 &[],
1698 0,
1699 );
1700 let rete = Rete::open(&bytes).unwrap();
1701
1702 let q = "PREFIX ex: <http://ex/> \
1704 SELECT ?f WHERE { GRAPH ex:social { ex:Alice ex:knows ?f } }";
1705 let (_, s) = eval_sparql(&rete, q).unwrap();
1706 assert_eq!(s.len(), 1);
1707 assert_eq!(s[0]["f"], "<http://ex/Bob>");
1708
1709 let q2 = "PREFIX ex: <http://ex/> \
1711 SELECT ?g WHERE { GRAPH ?g { ex:Bob ex:age ?a } }";
1712 let (_, s2) = eval_sparql(&rete, q2).unwrap();
1713 assert_eq!(s2.len(), 1);
1714 assert_eq!(s2[0]["g"], "<http://ex/profile>");
1715
1716 let q3 = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1719 GRAPH ex:profile { ?f ex:age ?a . FILTER EXISTS { ?f ex:age ?a2 } } }";
1720 assert_eq!(eval_sparql(&rete, q3).unwrap().1.len(), 1);
1721 let q4 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
1722 GRAPH ex:social { ?s ex:knows ?o . FILTER NOT EXISTS { ?s ex:age ?a } } }";
1723 assert_eq!(eval_sparql(&rete, q4).unwrap().1.len(), 1);
1725 }
1726
1727 #[test]
1728 fn from_unions_named_graphs() {
1729 use crate::write_dataset;
1730 let mut db = DictionaryBuilder::new();
1733 let t = [
1734 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1735 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1736 ];
1737 for (s, p, o) in t {
1738 db.observe(s, p, o);
1739 }
1740 let dict = db.build();
1741 let mut g1 = GraphIndexBuilder::new();
1742 g1.push(dict.encode(t[0].0, t[0].1, t[0].2).unwrap());
1743 let mut g2 = GraphIndexBuilder::new();
1744 g2.push(dict.encode(t[1].0, t[1].1, t[1].2).unwrap());
1745 let named = vec![
1746 ("<http://ex/social>".to_string(), g1.build()),
1747 ("<http://ex/profile>".to_string(), g2.build()),
1748 ];
1749 let bytes = write_dataset(
1750 &dict,
1751 &GraphIndexBuilder::new().build(),
1752 &named,
1753 true,
1754 &[],
1755 0,
1756 );
1757 let rete = Rete::open(&bytes).unwrap();
1758
1759 let q0 = "PREFIX ex: <http://ex/> SELECT ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1761 assert!(eval_sparql(&rete, q0).unwrap().1.is_empty());
1762
1763 let q = "PREFIX ex: <http://ex/> \
1765 SELECT ?z FROM ex:social FROM ex:profile \
1766 WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1767 let (_, s) = eval_sparql(&rete, q).unwrap();
1768 assert_eq!(s.len(), 1);
1769 assert_eq!(s[0]["z"], "<http://ex/Carol>");
1770
1771 let qn = "PREFIX ex: <http://ex/> SELECT ?g FROM NAMED ex:social \
1773 WHERE { GRAPH ?g { ?x ex:knows ?y } }";
1774 let (_, sn) = eval_sparql(&rete, qn).unwrap();
1775 let gs: Vec<&str> = sn.iter().map(|b| b["g"].as_str()).collect();
1776 assert_eq!(gs, vec!["<http://ex/social>"]); }
1778
1779 #[test]
1780 fn union_default_graph_opt_in() {
1781 use crate::write_dataset;
1782 let mut db = DictionaryBuilder::new();
1786 let t = [
1787 ("<http://ex/Root>", "<http://ex/knows>", "<http://ex/Alice>"),
1788 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1789 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1790 ];
1791 for (s, p, o) in t {
1792 db.observe(s, p, o);
1793 }
1794 let dict = db.build();
1795 let enc = |i: usize| dict.encode(t[i].0, t[i].1, t[i].2).unwrap();
1796 let mut def = GraphIndexBuilder::new();
1797 def.push(enc(0));
1798 let mut g1 = GraphIndexBuilder::new();
1799 g1.push(enc(1));
1800 let mut g2 = GraphIndexBuilder::new();
1801 g2.push(enc(1)); g2.push(enc(2));
1803 let named = vec![
1804 ("<http://ex/social>".to_string(), g1.build()),
1805 ("<http://ex/profile>".to_string(), g2.build()),
1806 ];
1807 let bytes = write_dataset(&dict, &def.build(), &named, true, &[], 0);
1808 let rete = Rete::open(&bytes).unwrap();
1809 let union = QueryOpts {
1810 union_default_graph: true,
1811 ..QueryOpts::default()
1812 };
1813 let rows = |out: QueryOutput| match out {
1814 QueryOutput::Select(_, rows) => rows,
1815 other => panic!("expected Select, got {other:?}"),
1816 };
1817
1818 let q = "PREFIX ex: <http://ex/> SELECT ?s ?o WHERE { ?s ex:knows ?o }";
1822 assert_eq!(rows(eval_query(&rete, q).unwrap()).len(), 1);
1823
1824 let got = rows(eval_query_with(&rete, q, union).unwrap());
1828 assert_eq!(got.len(), 3);
1829 assert!(got.iter().any(|b| b["s"] == "<http://ex/Root>"));
1830
1831 let qj = "PREFIX ex: <http://ex/> SELECT ?z WHERE { \
1833 ex:Root ex:knows ?x . ?x ex:knows ?y . ?y ex:knows ?z }";
1834 let sj = rows(eval_query_with(&rete, qj, union).unwrap());
1835 assert_eq!(sj.len(), 1);
1836 assert_eq!(sj[0]["z"], "<http://ex/Carol>");
1837
1838 let qg = "PREFIX ex: <http://ex/> SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ex:knows ?o } }";
1840 assert_eq!(rows(eval_query_with(&rete, qg, union).unwrap()).len(), 2);
1841
1842 let qf = "PREFIX ex: <http://ex/> SELECT ?s ?o FROM ex:social WHERE { ?s ex:knows ?o }";
1845 let sf = rows(eval_query_with(&rete, qf, union).unwrap());
1846 assert_eq!(sf.len(), 1);
1847 assert_eq!(sf[0]["s"], "<http://ex/Alice>");
1848
1849 let mut db2 = DictionaryBuilder::new();
1852 db2.observe(t[1].0, t[1].1, t[1].2);
1853 let dict2 = db2.build();
1854 let mut only = GraphIndexBuilder::new();
1855 only.push(dict2.encode(t[1].0, t[1].1, t[1].2).unwrap());
1856 let named2 = vec![("<http://ex/only>".to_string(), only.build())];
1857 let bytes2 = write_dataset(
1858 &dict2,
1859 &GraphIndexBuilder::new().build(),
1860 &named2,
1861 true,
1862 &[],
1863 0,
1864 );
1865 let rete2 = Rete::open(&bytes2).unwrap();
1866 assert_eq!(rows(eval_query(&rete2, q).unwrap()).len(), 0); assert_eq!(rows(eval_query_with(&rete2, q, union).unwrap()).len(), 1);
1868 }
1869
1870 #[test]
1871 fn describe_resource() {
1872 let bytes = rete_from(&[
1873 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1874 ("<http://ex/Alice>", "<http://ex/age>", "\"30\""),
1875 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1876 ]);
1877 let rete = Rete::open(&bytes).unwrap();
1878 match eval_query(&rete, "DESCRIBE <http://ex/Alice>").unwrap() {
1880 QueryOutput::Construct(t) => assert_eq!(t.len(), 2),
1881 other => panic!("expected Construct, got {other:?}"),
1882 }
1883 let q = "PREFIX ex: <http://ex/> DESCRIBE ?x WHERE { ?x ex:age ?a }";
1885 match eval_query(&rete, q).unwrap() {
1886 QueryOutput::Construct(t) => {
1887 assert_eq!(t.len(), 3);
1889 }
1890 other => panic!("expected Construct, got {other:?}"),
1891 }
1892 }
1893
1894 #[test]
1895 fn ask_and_construct() {
1896 let bytes = rete_from(&[
1897 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1898 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1899 ]);
1900 let rete = Rete::open(&bytes).unwrap();
1901
1902 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:knows ?b }").unwrap() {
1904 QueryOutput::Ask(b) => assert!(b),
1905 other => panic!("expected Ask, got {other:?}"),
1906 }
1907 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:likes ?b }").unwrap() {
1908 QueryOutput::Ask(b) => assert!(!b),
1909 other => panic!("expected Ask, got {other:?}"),
1910 }
1911
1912 let q = "PREFIX ex: <http://ex/> \
1914 CONSTRUCT { ?b ex:knownBy ?a } WHERE { ?a ex:knows ?b }";
1915 match eval_query(&rete, q).unwrap() {
1916 QueryOutput::Construct(mut triples) => {
1917 triples.sort();
1918 assert_eq!(triples.len(), 2);
1919 assert!(triples.contains(&(
1920 "<http://ex/Bob>".into(),
1921 "<http://ex/knownBy>".into(),
1922 "<http://ex/Alice>".into(),
1923 )));
1924 }
1925 other => panic!("expected Construct, got {other:?}"),
1926 }
1927 }
1928
1929 #[test]
1930 fn substr_strbefore_strafter() {
1931 let bytes = rete_from(&[("<http://ex/a>", "<http://ex/name>", "\"Alice Smith\"")]);
1932 let rete = Rete::open(&bytes).unwrap();
1933 let q = "PREFIX ex: <http://ex/> SELECT ?first ?last ?ini WHERE { \
1934 ?p ex:name ?n . \
1935 BIND(STRBEFORE(?n, \" \") AS ?first) \
1936 BIND(STRAFTER(?n, \" \") AS ?last) \
1937 BIND(SUBSTR(?n, 1, 1) AS ?ini) }";
1938 let (_, sols) = eval_sparql(&rete, q).unwrap();
1939 assert_eq!(sols[0]["first"], "\"Alice\"");
1941 assert_eq!(sols[0]["last"], "\"Smith\"");
1942 assert_eq!(sols[0]["ini"], "\"A\"");
1943 }
1944
1945 #[test]
1946 fn concat_and_coalesce() {
1947 let bytes = rete_from(&[
1949 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1950 ("<http://ex/Alice>", "<http://ex/nick>", "\"Al\""),
1951 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1952 ]);
1953 let rete = Rete::open(&bytes).unwrap();
1954 let q = "PREFIX ex: <http://ex/> SELECT ?label WHERE { \
1956 ?p ex:name ?name . OPTIONAL { ?p ex:nick ?nick } \
1957 BIND(CONCAT(\"@\", COALESCE(?nick, ?name)) AS ?label) }";
1958 let (_, sols) = eval_sparql(&rete, q).unwrap();
1959 let mut labels: Vec<&str> = sols.iter().map(|b| b["label"].as_str()).collect();
1960 labels.sort();
1961 assert_eq!(labels, vec!["\"@Al\"", "\"@Bob\""]);
1962 }
1963
1964 #[test]
1965 fn builtin_functions() {
1966 let bytes = rete_from(&[
1967 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice Smith\""),
1968 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob Jones\""),
1969 ]);
1970 let rete = Rete::open(&bytes).unwrap();
1971 let q = "PREFIX ex: <http://ex/> SELECT ?p ?len WHERE { \
1973 ?p ex:name ?n . FILTER(CONTAINS(?n, \"Smith\")) BIND(STRLEN(?n) AS ?len) }";
1974 let (_, sols) = eval_sparql(&rete, q).unwrap();
1975 assert_eq!(sols.len(), 1);
1976 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1977 assert_eq!(
1978 sols[0]["len"],
1979 "\"11\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1980 ); }
1982
1983 #[test]
1984 fn geosparql_filter_and_functions() {
1985 let wkt = "\"POLYGON((0 0,10 0,10 10,0 10,0 0))\"^^\
1986 <http://www.opengis.net/ont/geosparql#wktLiteral>";
1987 let bytes = rete_from(&[
1988 (
1989 "<http://ex/f>",
1990 "<http://www.opengis.net/ont/geosparql#hasGeometry>",
1991 "<http://ex/f/g>",
1992 ),
1993 (
1994 "<http://ex/f/g>",
1995 "<http://www.opengis.net/ont/geosparql#asWKT>",
1996 wkt,
1997 ),
1998 (
1999 "<http://ex/f>",
2000 "<http://ex/year>",
2001 "\"1815\"^^<http://www.w3.org/2001/XMLSchema#integer>",
2002 ),
2003 ]);
2004 let rete = Rete::open(&bytes).unwrap();
2005 let pre = "PREFIX geo: <http://www.opengis.net/ont/geosparql#> \
2006 PREFIX geof: <http://www.opengis.net/def/function/geosparql/> \
2007 PREFIX uom: <http://www.opengis.net/def/uom/OGC/1.0/> PREFIX ex: <http://ex/> ";
2008
2009 let (_, s) = eval_sparql(
2011 &rete,
2012 &format!(
2013 "{pre}SELECT ?f WHERE {{ ?f ex:year ?y ; \
2014 geo:hasGeometry/geo:asWKT ?w . \
2015 FILTER(?y = 1815 && geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral)) }}"
2016 ),
2017 )
2018 .unwrap();
2019 assert_eq!(s.len(), 1, "point inside the polygon in year 1815");
2020 assert_eq!(s[0]["f"], "<http://ex/f>");
2021
2022 let (_, s) = eval_sparql(
2024 &rete,
2025 &format!(
2026 "{pre}SELECT ?f WHERE {{ \
2027 ?f geo:hasGeometry/geo:asWKT ?w . \
2028 FILTER(geof:sfContains(?w, \"POINT(50 50)\"^^geo:wktLiteral)) }}"
2029 ),
2030 )
2031 .unwrap();
2032 assert!(s.is_empty());
2033
2034 let (_, s) = eval_sparql(
2036 &rete,
2037 &format!(
2038 "{pre}SELECT ?f WHERE {{ \
2039 ?f geo:hasGeometry/geo:asWKT ?w . \
2040 FILTER(geof:sfWithin(\"POINT(5 5)\"^^geo:wktLiteral, ?w)) }}"
2041 ),
2042 )
2043 .unwrap();
2044 assert_eq!(s.len(), 1);
2045
2046 let (_, s) = eval_sparql(
2048 &rete,
2049 &format!(
2050 "{pre}SELECT ?f WHERE {{ \
2051 ?f geo:hasGeometry/geo:asWKT ?w . \
2052 FILTER(geof:sfContains(?w, \"garbage\"^^geo:wktLiteral)) }}"
2053 ),
2054 )
2055 .unwrap();
2056 assert!(s.is_empty());
2057
2058 let (_, s) = eval_sparql(
2060 &rete,
2061 &format!(
2062 "{pre}SELECT ?hit WHERE {{ \
2063 ?f geo:hasGeometry/geo:asWKT ?w . \
2064 BIND(geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral) AS ?hit) }}"
2065 ),
2066 )
2067 .unwrap();
2068 assert_eq!(
2069 s[0]["hit"],
2070 "\"true\"^^<http://www.w3.org/2001/XMLSchema#boolean>"
2071 );
2072
2073 let (_, s) = eval_sparql(
2075 &rete,
2076 &format!(
2077 "{pre}SELECT ?d WHERE {{ BIND(geof:distance(\
2078 \"POINT(0 0)\"^^geo:wktLiteral, \"POINT(0 1)\"^^geo:wktLiteral, uom:metre) AS ?d) }}"
2079 ),
2080 )
2081 .unwrap();
2082 assert!(
2083 s[0]["d"].ends_with("XMLSchema#double>"),
2084 "distance is xsd:double: {}",
2085 s[0]["d"]
2086 );
2087 let (_, s) = eval_sparql(
2088 &rete,
2089 &format!(
2090 "{pre}SELECT ?e WHERE {{ \
2091 ?f geo:hasGeometry/geo:asWKT ?w . BIND(geof:envelope(?w) AS ?e) }}"
2092 ),
2093 )
2094 .unwrap();
2095 assert!(s[0]["e"].contains("wktLiteral") && s[0]["e"].contains("POLYGON"));
2096
2097 assert!(eval_sparql(
2099 &rete,
2100 &format!(
2101 "{pre}SELECT ?x WHERE {{ \
2102 BIND(geof:buffer(\"POINT(0 0)\"^^geo:wktLiteral, 1) AS ?x) }}"
2103 )
2104 )
2105 .is_err());
2106 }
2107
2108 #[test]
2109 fn bind_arithmetic() {
2110 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2111 let bytes = rete_from(&[(
2112 "<http://ex/a>",
2113 "<http://ex/age>",
2114 &format!("\"30\"^^{xsd}"),
2115 )]);
2116 let rete = Rete::open(&bytes).unwrap();
2117 let q = "PREFIX ex: <http://ex/> \
2119 SELECT ?next WHERE { ?p ex:age ?age . BIND(?age + 1 AS ?next) FILTER(?age * 2 > 50) }";
2120 let (_, sols) = eval_sparql(&rete, q).unwrap();
2121 assert_eq!(sols.len(), 1);
2122 assert_eq!(
2123 sols[0]["next"],
2124 "\"31\"^^<http://www.w3.org/2001/XMLSchema#integer>"
2125 );
2126 }
2127
2128 #[test]
2129 fn bind_value_is_visible_to_a_following_filter_and_join() {
2130 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2134 let n = |v: i32| format!("\"{v}\"^^{xsd}");
2135 let bytes = rete_from(&[
2136 ("<http://ex/a>", "<http://ex/v>", &n(1)),
2137 ("<http://ex/b>", "<http://ex/v>", &n(2)),
2138 ("<http://ex/c>", "<http://ex/v>", &n(3)),
2139 ("<http://ex/x>", "<http://ex/v>", &n(3)),
2141 ]);
2142 let rete = Rete::open(&bytes).unwrap();
2143
2144 let q1 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
2146 ?s ex:v ?o . BIND(?o + 1 AS ?z) FILTER(?z = 3) }";
2147 let (_, s1) = eval_sparql(&rete, q1).unwrap();
2148 assert_eq!(s1.len(), 1, "only b (2+1=3) passes");
2149 assert_eq!(s1[0]["s"], "<http://ex/b>");
2150
2151 let q2 = "PREFIX ex: <http://ex/> SELECT ?s ?s2 WHERE { \
2153 ?s ex:v ?o . BIND(?o + 1 AS ?z) ?s2 ex:v ?z }";
2154 let (_, s2) = eval_sparql(&rete, q2).unwrap();
2155 let mut pairs: Vec<(String, String)> = s2
2157 .iter()
2158 .map(|b| (b["s"].clone(), b["s2"].clone()))
2159 .collect();
2160 pairs.sort();
2161 assert_eq!(
2162 pairs,
2163 vec![
2164 ("<http://ex/a>".to_string(), "<http://ex/b>".to_string()),
2165 ("<http://ex/b>".to_string(), "<http://ex/c>".to_string()),
2166 ("<http://ex/b>".to_string(), "<http://ex/x>".to_string()),
2167 ]
2168 );
2169 }
2170
2171 #[test]
2172 fn order_by_numeric_desc_then_limit() {
2173 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2174 let bytes = rete_from(&[
2175 (
2176 "<http://ex/a>",
2177 "<http://ex/age>",
2178 &format!("\"30\"^^{xsd}"),
2179 ),
2180 (
2181 "<http://ex/b>",
2182 "<http://ex/age>",
2183 &format!("\"25\"^^{xsd}"),
2184 ),
2185 (
2186 "<http://ex/c>",
2187 "<http://ex/age>",
2188 &format!("\"40\"^^{xsd}"),
2189 ),
2190 ]);
2191 let rete = Rete::open(&bytes).unwrap();
2192 let q = "PREFIX ex: <http://ex/> \
2194 SELECT ?p WHERE { ?p ex:age ?age } ORDER BY DESC(?age) LIMIT 2";
2195 let (_, sols) = eval_sparql(&rete, q).unwrap();
2196 let ps: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
2197 assert_eq!(ps, vec!["<http://ex/c>", "<http://ex/a>"]); }
2199
2200 #[test]
2201 fn limit_early_out_two_hop_join() {
2202 let bytes = rete_from(&[
2206 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
2207 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
2208 ("<http://ex/B>", "<http://ex/k>", "<http://ex/D>"),
2209 ("<http://ex/C>", "<http://ex/k>", "<http://ex/E>"),
2210 ]);
2211 let rete = Rete::open(&bytes).unwrap();
2212 let q = "PREFIX ex: <http://ex/> SELECT ?x ?z WHERE { ?x ex:k ?y . ?y ex:k ?z }";
2213 let (_, full) = eval_sparql(&rete, q).unwrap();
2214 assert_eq!(full.len(), 3);
2215
2216 let (_, one) = eval_sparql(&rete, &format!("{q} LIMIT 1")).unwrap();
2217 assert_eq!(one.len(), 1);
2218 assert!(one.iter().all(|r| full.contains(r)));
2220
2221 let (_, all) = eval_sparql(&rete, &format!("{q} LIMIT 100")).unwrap();
2223 assert_eq!(all.len(), 3);
2224 let (_, off) = eval_sparql(&rete, &format!("{q} LIMIT 100 OFFSET 2")).unwrap();
2225 assert_eq!(off.len(), 1);
2226 }
2227
2228 #[test]
2229 fn limit_early_out_filter_over_bgp() {
2230 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2233 let bytes = rete_from(&[
2234 ("<http://ex/a>", "<http://ex/n>", &format!("\"10\"^^{xsd}")),
2235 ("<http://ex/b>", "<http://ex/n>", &format!("\"20\"^^{xsd}")),
2236 ("<http://ex/c>", "<http://ex/n>", &format!("\"30\"^^{xsd}")),
2237 ("<http://ex/d>", "<http://ex/n>", &format!("\"40\"^^{xsd}")),
2238 ]);
2239 let rete = Rete::open(&bytes).unwrap();
2240 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { ?p ex:n ?v FILTER(?v > 15) }";
2241 let (_, full) = eval_sparql(&rete, q).unwrap();
2242 assert_eq!(full.len(), 3); let (_, two) = eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap();
2244 assert_eq!(two.len(), 2);
2245 assert!(two.iter().all(|r| full.contains(r)));
2246 }
2247
2248 #[test]
2249 fn distinct_bgp_fast_matches_general() {
2250 let bytes = rete_from(&[
2253 ("<http://ex/a>", "<http://ex/p>", "<http://ex/x>"),
2254 ("<http://ex/b>", "<http://ex/p>", "<http://ex/x>"),
2255 ("<http://ex/b>", "<http://ex/p>", "<http://ex/y>"),
2256 ("<http://ex/c>", "<http://ex/p>", "<http://ex/z>"),
2257 ]);
2258 let rete = Rete::open(&bytes).unwrap();
2259 let q = "PREFIX ex: <http://ex/> SELECT DISTINCT ?o WHERE { ?s ex:p ?o }";
2261 let (proj, sols) = eval_sparql(&rete, q).unwrap();
2262 assert_eq!(proj, vec!["o"]);
2263 let mut os: Vec<&str> = sols.iter().map(|b| b["o"].as_str()).collect();
2264 os.sort();
2265 assert_eq!(os, vec!["<http://ex/x>", "<http://ex/y>", "<http://ex/z>"]);
2266 assert_eq!(
2268 eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap().1.len(),
2269 2
2270 );
2271 let q2 = "PREFIX ex: <http://ex/> SELECT DISTINCT ?s ?o WHERE { ?s ex:p ?o }";
2273 assert_eq!(eval_sparql(&rete, q2).unwrap().1.len(), 4);
2274 }
2275
2276 #[test]
2277 fn limit_caps_solutions() {
2278 let bytes = rete_from(&[
2279 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
2280 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
2281 ("<http://ex/c>", "<http://ex/p>", "<http://ex/3>"),
2282 ]);
2283 let rete = Rete::open(&bytes).unwrap();
2284 let (_, sols) =
2285 eval_sparql(&rete, "SELECT ?x WHERE { ?x <http://ex/p> ?y } LIMIT 2").unwrap();
2286 assert_eq!(sols.len(), 2);
2287 }
2288
2289 #[test]
2296 fn string_filters_see_past_an_embedded_quote() {
2297 let bytes = rete_from(&[
2298 (
2299 "<http://ex/a>",
2300 "<http://ex/label>",
2301 "\"plain Quantum here\"",
2302 ),
2303 (
2304 "<http://ex/b>",
2305 "<http://ex/label>",
2306 r#""He said \"Quantum\" loudly"@en"#,
2307 ),
2308 (
2309 "<http://ex/c>",
2310 "<http://ex/label>",
2311 r#""Theory of \"Quantum\" Gases""#,
2312 ),
2313 ("<http://ex/d>", "<http://ex/label>", "\"no match at all\""),
2314 (
2315 "<http://ex/e>",
2316 "<http://ex/label>",
2317 "\"tab\\there\\nline two\"^^<http://www.w3.org/2001/XMLSchema#string>",
2318 ),
2319 ]);
2320 let rete = Rete::open(&bytes).unwrap();
2321 let subjects = |filter: &str| {
2322 let q = format!("SELECT ?s WHERE {{ ?s <http://ex/label> ?l FILTER({filter}) }}");
2323 let (_, sols) = eval_sparql(&rete, &q).unwrap();
2324 let mut out: Vec<String> = sols.iter().map(|b| b["s"].clone()).collect();
2325 out.sort();
2326 out
2327 };
2328 let (a, b, c, e) = (
2329 "<http://ex/a>",
2330 "<http://ex/b>",
2331 "<http://ex/c>",
2332 "<http://ex/e>",
2333 );
2334 assert_eq!(subjects(r#"CONTAINS(?l, "Quantum")"#), vec![a, b, c]);
2335 assert_eq!(subjects(r#"CONTAINS(?l, "Gases")"#), vec![c]);
2336 assert_eq!(subjects(r#"STRENDS(?l, "loudly")"#), vec![b]);
2337 assert_eq!(subjects(r#"STRSTARTS(?l, "Theory of \"Q")"#), vec![c]);
2338 assert_eq!(subjects(r#"REGEX(?l, "gases$", "i")"#), vec![c]);
2339 assert_eq!(subjects(r#"REGEX(?l, "^line two$", "m")"#), vec![e]);
2340 assert_eq!(subjects(r#"CONTAINS(?l, "\there")"#), vec![e]);
2341 assert_eq!(subjects(r#"LANGMATCHES(LANG(?l), "en")"#), vec![b]);
2342 assert_eq!(subjects(r#"STRAFTER(?l, "Quantum") = "\" Gases""#), vec![c]);
2343 assert_eq!(
2344 subjects(r#"STRBEFORE(?l, " loudly") = "He said \"Quantum\""@en"#),
2345 vec![b]
2346 );
2347 assert_eq!(subjects(r#"STRLEN(?l) = 25"#), vec![c]);
2348 assert_eq!(subjects(r#"STRLEN(?l) = 24"#), vec![b]);
2349 }
2350
2351 #[test]
2359 fn reasoning_rewrites_only_the_atoms_it_can_extend() {
2360 let dom = "<http://www.w3.org/2000/01/rdf-schema#domain>";
2361 let sub = "<http://www.w3.org/2000/01/rdf-schema#subClassOf>";
2362 let ty = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
2363 let bytes = rete_from(&[
2364 (
2365 "<http://ex/hasCharacterisation>",
2366 dom,
2367 "<http://ex/Citation>",
2368 ),
2369 ("<http://ex/SelfCitation>", sub, "<http://ex/Citation>"),
2370 ("<http://ex/writtenBy>", dom, "<http://ex/Work>"),
2371 ("<http://ex/r1>", ty, "<http://ex/Role>"),
2372 ("<http://ex/w1>", ty, "<http://ex/Work>"),
2373 ("<http://ex/w2>", "<http://ex/writtenBy>", "<http://ex/ann>"),
2374 ]);
2375 let rete = Rete::open(&bytes).unwrap();
2376 let rewrite = |q: &str| {
2377 let sel = parse_select(q).unwrap();
2378 ql::reason_rewrite(sel.plan, &rete, &sel.project)
2379 };
2380 assert!(matches!(
2382 rewrite("SELECT ?s WHERE { ?s a <http://ex/Role> ; ?p ?o }"),
2383 Plan::Bgp(p) if p.len() == 2
2384 ));
2385 assert!(matches!(
2388 rewrite("SELECT ?s WHERE { ?s a <http://ex/Citation> }"),
2389 Plan::Bgp(p) if p.len() == 1
2390 ));
2391 match rewrite("SELECT ?s WHERE { ?s a <http://ex/Work> }") {
2393 Plan::DistinctUnion { vars, sources } => {
2394 assert_eq!(vars, vec!["s".to_string()]);
2395 assert_eq!(sources.len(), 2);
2396 }
2397 other => panic!("expected a DistinctUnion, got {other:?}"),
2398 }
2399 let (_, sols) =
2400 eval_sparql_reasoned(&rete, "SELECT ?s WHERE { ?s a <http://ex/Work> }").unwrap();
2401 let mut got: Vec<&str> = sols.iter().map(|b| b["s"].as_str()).collect();
2402 got.sort();
2403 assert_eq!(got, vec!["<http://ex/w1>", "<http://ex/w2>"]);
2404 }
2405
2406 #[test]
2411 fn a_small_left_side_probes_the_right_bgp() {
2412 let mut t: Vec<(String, String, String)> = Vec::new();
2413 let e = |n: String| format!("<http://ex/{n}>");
2414 for i in 0..200 {
2415 t.push((e(format!("j{i}")), e("a".into()), e("Journal".into())));
2416 t.push((e(format!("j{i}")), e("title".into()), format!("\"T{i}\"")));
2417 t.push((e(format!("j{i}")), e("id".into()), e(format!("id{i}"))));
2418 t.push((e(format!("id{i}")), e("value".into()), format!("\"{i}\"")));
2419 }
2420 t.push((e("work".into()), e("partOf".into()), e("issue".into())));
2421 t.push((e("issue".into()), e("partOf".into()), e("j7".into())));
2422 let refs: Vec<(&str, &str, &str)> = t
2423 .iter()
2424 .map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
2425 .collect();
2426 let rete = Rete::open(&rete_from(&refs)).unwrap();
2427 let queries = [
2428 "PREFIX ex: <http://ex/> SELECT ?j ?t ?v WHERE { ex:work ex:partOf+ ?j . \
2429 ?j ex:a ex:Journal ; ex:title ?t ; ex:id ?i . ?i ex:value ?v }",
2430 "PREFIX ex: <http://ex/> SELECT ?j ?t WHERE { ex:work ex:partOf+ ?j \
2431 OPTIONAL { ?j ex:title ?t } }",
2432 ];
2433 for q in queries {
2434 let run = |floor: f64| {
2435 crate::bgp::with_probe_floor(floor, || {
2436 let (_, sols) = eval_sparql(&rete, q).unwrap();
2437 let mut v: Vec<String> = sols.iter().map(|b| format!("{b:?}")).collect();
2438 v.sort();
2439 v
2440 })
2441 };
2442 let hashed = run(f64::MAX);
2443 assert!(!hashed.is_empty());
2444 assert_eq!(run(0.0), hashed, "{q}");
2445 }
2446 }
2447}