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