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>,
406 loaded: bool,
407 probe: Option<ExistsProbe>,
408 corr: Option<crate::bgp::CorrelatedProbe>,
411}
412
413struct ExistsProbe {
417 svars: Vec<usize>,
419 jvars: Vec<usize>,
421 keys: std::collections::HashSet<Vec<crate::row::Val>>,
423 partial: Vec<crate::row::Row>,
425}
426
427fn build_exists_probe(b: &crate::row::Row, sols: &[crate::row::Row]) -> ExistsProbe {
430 let mask = crate::row::bound_mask(sols, b.len());
431 let svars: Vec<usize> = (0..b.len()).filter(|&i| mask[i]).collect();
432 let jvars: Vec<usize> = svars.iter().copied().filter(|&i| b[i].is_some()).collect();
433 let mut keys = std::collections::HashSet::new();
434 let mut partial = Vec::new();
435 for s in sols {
436 match jvars
437 .iter()
438 .map(|&i| s[i].clone())
439 .collect::<Option<Vec<crate::row::Val>>>()
440 {
441 Some(k) => {
442 keys.insert(k);
443 }
444 None => partial.push(s.clone()),
445 }
446 }
447 ExistsProbe {
448 svars,
449 jvars,
450 keys,
451 partial,
452 }
453}
454
455fn exists_matches(b: &crate::row::Row, entry: &ExistsEntry) -> bool {
459 let probe = entry.probe.as_ref().unwrap();
460 let bj: Vec<usize> = probe
461 .svars
462 .iter()
463 .copied()
464 .filter(|&i| b[i].is_some())
465 .collect();
466 if bj == probe.jvars {
467 let k: Vec<crate::row::Val> = probe.jvars.iter().map(|&i| b[i].clone().unwrap()).collect();
468 probe.keys.contains(&k)
469 || probe
470 .partial
471 .iter()
472 .any(|s| crate::row::compatible_rows(b, s))
473 } else {
474 entry.sols.iter().any(|s| crate::row::compatible_rows(b, s))
475 }
476}
477
478fn lexical(token: &str) -> String {
485 crate::terms::literal_lexical(token).unwrap_or_else(|| token.to_string())
486}
487
488pub(crate) fn term_number(s: &str) -> Option<f64> {
493 value::numeric(s)
494}
495
496use crate::terms::as_number;
497
498#[derive(Debug, Clone)]
500#[must_use]
501#[non_exhaustive]
502pub enum QueryOutput {
503 Select(Vec<String>, Vec<Binding>),
505 Ask(bool),
507 Construct(Vec<(String, String, String)>),
509}
510
511#[derive(Debug, Clone, PartialEq, Eq)]
514#[must_use]
515#[non_exhaustive]
516pub enum SummaryQueryShape {
517 PredicateCount { predicate: String, variable: String },
519 TripleCount { variable: String },
521 PredicateTotals {
523 predicate_variable: String,
524 count_variable: String,
525 },
526 PredicateList { variable: String },
528 PredicateDistinctCount { variable: String },
530 TripleExists,
532 PredicateExists { predicate: String },
534}
535
536#[derive(Debug, Clone, PartialEq, Eq)]
540pub struct RoutedTriplePattern {
541 pub subject: Option<String>,
542 pub predicate: Option<String>,
543 pub object: Option<String>,
544}
545
546pub fn routed_triple_pattern(query: &str) -> Result<Option<RoutedTriplePattern>, SparqlError> {
551 let parsed = parse_query(query)?;
552 let sel = match parsed {
553 Query::Select {
554 pattern, dataset, ..
555 } => lower_select(&pattern, &dataset)?,
556 Query::Ask { pattern, .. } => lower_pattern(&pattern)?,
557 Query::Construct { pattern, .. } => lower_pattern(&pattern)?,
558 Query::Describe { pattern, .. } => lower_pattern(&pattern)?,
559 };
560 if !sel.from.is_empty() || sel.from_named.is_some() {
561 return Ok(None);
562 }
563 let Plan::Bgp(patterns) = sel.plan else {
564 return Ok(None);
565 };
566 let [tp] = patterns.as_slice() else {
567 return Ok(None);
568 };
569 Ok(Some(RoutedTriplePattern {
570 subject: term_const(&tp.s),
571 predicate: term_const(&tp.p),
572 object: term_const(&tp.o),
573 }))
574}
575
576fn term_const(term: &PatternTerm) -> Option<String> {
577 match term {
578 PatternTerm::Const(t) => Some(t.clone()),
579 PatternTerm::Var(_) => None,
580 }
581}
582
583pub fn summary_query_shape(query: &str) -> Result<Option<SummaryQueryShape>, SparqlError> {
590 let parsed = parse_query(query)?;
591 match parsed {
592 Query::Select {
593 pattern, dataset, ..
594 } => {
595 let sel = lower_select(&pattern, &dataset)?;
596 if !sel.from.is_empty()
597 || sel.from_named.is_some()
598 || sel.offset != 0
599 || sel.limit.is_some()
600 || !sel.order.is_empty()
601 || !sel.having.is_empty()
602 {
603 return Ok(None);
604 }
605 if sel.distinct {
606 if sel.group.is_some() || !sel.extends.is_empty() {
607 return Ok(None);
608 }
609 let [projected] = sel.project.as_slice() else {
610 return Ok(None);
611 };
612 let Some(SummaryPatternShape::AnyPredicate { variable }) =
613 single_summary_pattern(&sel.plan)
614 else {
615 return Ok(None);
616 };
617 return if projected == &variable {
618 Ok(Some(SummaryQueryShape::PredicateList { variable }))
619 } else {
620 Ok(None)
621 };
622 }
623 let Some(group) = &sel.group else {
624 return Ok(None);
625 };
626 if group.aggs.len() != 1 {
627 return Ok(None);
628 }
629 match group.by.as_slice() {
630 [] => match &group.aggs[0].1 {
631 Agg::CountStar { distinct: false } => {
632 let Some(variable) = public_aggregate_variable(&sel, &group.aggs[0].0)
633 else {
634 return Ok(None);
635 };
636 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
637 SummaryPatternShape::Predicate(predicate) => {
638 SummaryQueryShape::PredicateCount {
639 predicate,
640 variable,
641 }
642 }
643 SummaryPatternShape::AnyPredicate { .. } => {
644 SummaryQueryShape::TripleCount { variable }
645 }
646 }))
647 }
648 Agg::Count(counted, true) => {
649 let Some(public_variable) =
650 public_aggregate_variable(&sel, &group.aggs[0].0)
651 else {
652 return Ok(None);
653 };
654 let Some(SummaryPatternShape::AnyPredicate { variable }) =
655 single_summary_pattern(&sel.plan)
656 else {
657 return Ok(None);
658 };
659 if &variable != counted {
660 return Ok(None);
661 }
662 Ok(Some(SummaryQueryShape::PredicateDistinctCount {
663 variable: public_variable,
664 }))
665 }
666 _ => Ok(None),
667 },
668 [group_var] => {
669 if !matches!(group.aggs[0].1, Agg::CountStar { distinct: false }) {
670 return Ok(None);
671 }
672 let Some(SummaryPatternShape::AnyPredicate { variable }) =
673 single_summary_pattern(&sel.plan)
674 else {
675 return Ok(None);
676 };
677 if &variable != group_var {
678 return Ok(None);
679 }
680 let Some(count_variable) =
681 public_group_aggregate_variable(&sel, &group.aggs[0].0, group_var)
682 else {
683 return Ok(None);
684 };
685 Ok(Some(SummaryQueryShape::PredicateTotals {
686 predicate_variable: group_var.clone(),
687 count_variable,
688 }))
689 }
690 _ => Ok(None),
691 }
692 }
693 Query::Ask { pattern, .. } => {
694 let sel = lower_pattern(&pattern)?;
695 Ok(single_summary_pattern(&sel.plan).map(|shape| match shape {
696 SummaryPatternShape::Predicate(predicate) => {
697 SummaryQueryShape::PredicateExists { predicate }
698 }
699 SummaryPatternShape::AnyPredicate { .. } => SummaryQueryShape::TripleExists,
700 }))
701 }
702 Query::Construct { .. } | Query::Describe { .. } => Ok(None),
703 }
704}
705
706enum SummaryPatternShape {
707 Predicate(String),
708 AnyPredicate { variable: String },
709}
710
711fn single_summary_pattern(plan: &Plan) -> Option<SummaryPatternShape> {
712 let Plan::Bgp(patterns) = plan else {
713 return None;
714 };
715 let [tp] = patterns.as_slice() else {
716 return None;
717 };
718 let (PatternTerm::Var(s), PatternTerm::Var(o)) = (&tp.s, &tp.o) else {
719 return None;
720 };
721 if s == o {
722 return None;
723 }
724 match &tp.p {
725 PatternTerm::Const(p) => Some(SummaryPatternShape::Predicate(p.clone())),
726 PatternTerm::Var(p) if p != s && p != o => Some(SummaryPatternShape::AnyPredicate {
727 variable: p.clone(),
728 }),
729 _ => None,
730 }
731}
732
733fn public_aggregate_variable(sel: &Select, aggregate_var: &str) -> Option<String> {
734 let [projected] = sel.project.as_slice() else {
735 return None;
736 };
737 if projected == aggregate_var {
738 return Some(projected.clone());
739 }
740 sel.extends.iter().find_map(|(var, expr)| match expr {
741 FExpr::Var(source) if var == projected && source == aggregate_var => Some(var.clone()),
742 _ => None,
743 })
744}
745
746fn public_group_aggregate_variable(
747 sel: &Select,
748 aggregate_var: &str,
749 group_var: &str,
750) -> Option<String> {
751 let [projected_group, projected_aggregate] = sel.project.as_slice() else {
752 return None;
753 };
754 if projected_group != group_var {
755 return None;
756 }
757 if projected_aggregate == aggregate_var {
758 return Some(projected_aggregate.clone());
759 }
760 sel.extends.iter().find_map(|(var, expr)| match expr {
761 FExpr::Var(source) if var == projected_aggregate && source == aggregate_var => {
762 Some(var.clone())
763 }
764 _ => None,
765 })
766}
767
768#[derive(Debug, Clone, Copy)]
771pub struct CommunityPartial {
772 pub community: usize,
773 pub subjects: usize,
774 pub rows: usize,
775}
776
777pub type CommunitySelect = (Vec<String>, Vec<Binding>, Vec<CommunityPartial>);
780
781pub fn eval_select_communities(
792 rete: &Rete,
793 query: &str,
794 round: Option<usize>,
795) -> Result<CommunitySelect, SparqlError> {
796 let parsed = parse_query(query)?;
797 let out = match parsed {
798 Query::Select {
799 pattern, dataset, ..
800 } => run_select_communities(rete, &lower_select(&pattern, &dataset)?, round),
801 _ => Err(SparqlError::Unsupported(
802 "community-split evaluation supports SELECT queries only",
803 )),
804 };
805 match rete.take_service_error() {
808 Some(e) => Err(SparqlError::Service(e)),
809 None => out,
810 }
811}
812
813pub fn eval_query(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
815 eval_query_with(rete, query, QueryOpts::default())
816}
817
818pub fn eval_query_reasoned(rete: &Rete, query: &str) -> Result<QueryOutput, SparqlError> {
823 eval_query_with(
824 rete,
825 query,
826 QueryOpts {
827 reason: true,
828 ..QueryOpts::default()
829 },
830 )
831}
832
833#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
837pub struct QueryOpts {
838 pub reason: bool,
840 pub union_default_graph: bool,
846}
847
848pub fn eval_query_with(
851 rete: &Rete,
852 query: &str,
853 opts: QueryOpts,
854) -> Result<QueryOutput, SparqlError> {
855 diag::begin();
859 let out = eval_query_inner(rete, query, opts);
860 match rete.take_service_error() {
864 Some(e) => Err(SparqlError::Service(e)),
865 None => out,
866 }
867}
868
869pub fn eval_query_with_warnings(
886 rete: &Rete,
887 query: &str,
888 opts: QueryOpts,
889) -> Result<(QueryOutput, Vec<QueryWarning>), SparqlError> {
890 let out = eval_query_with(rete, query, opts)?;
891 let warnings = diag::finish(&out);
892 Ok((out, warnings))
893}
894
895pub fn partition_plan(rete: &Rete, query: &str, var: &str) -> Result<PartitionPlan, SparqlError> {
911 partition::plan(rete, query, var)
912}
913
914pub fn eval_query_partitioned(
924 rete: &Rete,
925 query: &str,
926 opts: QueryOpts,
927 var: &str,
928 keys: &[String],
929) -> Result<(QueryOutput, Vec<QueryWarning>), SparqlError> {
930 diag::begin();
931 let out = eval_partition_inner(rete, query, opts, partition::var_name(var), keys);
932 let out = match rete.take_service_error() {
933 Some(e) => Err(SparqlError::Service(e)),
934 None => out,
935 }?;
936 let warnings = diag::finish(&out);
937 Ok((out, warnings))
938}
939
940fn eval_partition_inner(
941 rete: &Rete,
942 query: &str,
943 opts: QueryOpts,
944 var: &str,
945 keys: &[String],
946) -> Result<QueryOutput, SparqlError> {
947 let parsed = parse_query(query)?;
948 partition::check(&parsed, var)?;
949 let chunk = |sel: Select| -> Select {
952 let mut sel = maybe_reason(rete, apply_opts(sel, opts), opts.reason);
953 sel.offset = 0;
954 sel.limit = None;
955 sel.plan = partition::seed(sel.plan, var, keys);
956 sel
957 };
958 match parsed {
959 Query::Select {
960 pattern, dataset, ..
961 } => {
962 let (vars, rows) = run_select(rete, &chunk(lower_select(&pattern, &dataset)?));
963 Ok(QueryOutput::Select(vars, rows))
964 }
965 Query::Construct {
966 template, pattern, ..
967 } => {
968 let sel = chunk(lower_pattern(&pattern)?);
969 let (ctx, sols) = raw_solutions(rete, &sel);
970 Ok(QueryOutput::Construct(instantiate(&ctx, &template, &sols)))
971 }
972 _ => Err(SparqlError::Unsupported(
974 "only SELECT and CONSTRUCT can be split",
975 )),
976 }
977}
978
979fn maybe_reason(rete: &Rete, mut sel: Select, reason: bool) -> Select {
982 if reason {
983 let projected = sel.project.clone();
984 sel.plan = ql::reason_rewrite(sel.plan, rete, &projected);
985 }
986 sel
987}
988
989fn apply_opts(mut sel: Select, opts: QueryOpts) -> Select {
994 sel.union_default = opts.union_default_graph;
995 sel
996}
997
998fn eval_query_inner(rete: &Rete, query: &str, opts: QueryOpts) -> Result<QueryOutput, SparqlError> {
999 let reason = opts.reason;
1000 let parsed = parse_query(query)?;
1001 match parsed {
1002 Query::Select {
1003 pattern, dataset, ..
1004 } => {
1005 let (vars, rows) = run_select(
1006 rete,
1007 &maybe_reason(
1008 rete,
1009 apply_opts(lower_select(&pattern, &dataset)?, opts),
1010 reason,
1011 ),
1012 );
1013 Ok(QueryOutput::Select(vars, rows))
1014 }
1015 Query::Ask { pattern, .. } => {
1016 let sel = maybe_reason(rete, apply_opts(lower_pattern(&pattern)?, opts), reason);
1017 Ok(QueryOutput::Ask(ask_solution(rete, &sel)))
1018 }
1019 Query::Construct {
1020 template, pattern, ..
1021 } => {
1022 let sel = maybe_reason(rete, apply_opts(lower_pattern(&pattern)?, opts), reason);
1023 let (ctx, sols) = raw_solutions(rete, &sel);
1024 Ok(QueryOutput::Construct(instantiate(&ctx, &template, &sols)))
1025 }
1026 Query::Describe {
1027 pattern, dataset, ..
1028 } => {
1029 let sel = maybe_reason(
1032 rete,
1033 apply_opts(lower_select(&pattern, &dataset)?, opts),
1034 reason,
1035 );
1036 let (ctx, rows) = raw_solutions(rete, &sel);
1037 let mut resources = std::collections::BTreeSet::new();
1038 for row in &rows {
1039 if sel.project.is_empty() {
1040 for val in row.iter().flatten() {
1041 if let Some(t) = ctx.resolver.str_of(val) {
1042 resources.insert(t.to_string());
1043 }
1044 }
1045 } else {
1046 for v in &sel.project {
1047 if let Some(val) = ctx.slots.slot(v).and_then(|s| row[s].as_ref()) {
1048 if let Some(t) = ctx.resolver.str_of(val) {
1049 resources.insert(t.to_string());
1050 }
1051 }
1052 }
1053 }
1054 }
1055 let mut graphs: Vec<Option<&str>> = Vec::new();
1060 if !sel.from.is_empty() {
1061 graphs.extend(sel.from.iter().map(|g| Some(g.as_str())));
1062 } else {
1063 graphs.push(None);
1064 if sel.union_default {
1065 graphs.extend(rete.graph_names().into_iter().map(Some));
1066 }
1067 }
1068 let mut triples = std::collections::BTreeSet::new();
1069 for r in &resources {
1070 for g in &graphs {
1071 for t in rete.query_in_graph(*g, Some(r), None, None) {
1072 triples.insert(t);
1073 }
1074 }
1075 }
1076 Ok(QueryOutput::Construct(triples.into_iter().collect()))
1077 }
1078 }
1079}
1080
1081pub fn eval_sparql(rete: &Rete, query: &str) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
1085 eval_sparql_opts(rete, query, false)
1086}
1087
1088pub fn eval_sparql_reasoned(
1090 rete: &Rete,
1091 query: &str,
1092) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
1093 eval_sparql_opts(rete, query, true)
1094}
1095
1096fn eval_sparql_opts(
1097 rete: &Rete,
1098 query: &str,
1099 reason: bool,
1100) -> Result<(Vec<String>, Vec<Binding>), SparqlError> {
1101 let sel = maybe_reason(rete, parse_select(query)?, reason);
1102 let out = run_select(rete, &sel);
1103 match rete.take_service_error() {
1105 Some(e) => Err(SparqlError::Service(e)),
1106 None => Ok(out),
1107 }
1108}
1109
1110pub(crate) fn fmt_num_typed(x: f64) -> String {
1116 if x.fract() == 0.0 {
1117 format!(
1118 "\"{}\"^^<http://www.w3.org/2001/XMLSchema#integer>",
1119 x as i64
1120 )
1121 } else {
1122 let cleaned: f64 = format!("{x:.14e}").parse().unwrap_or(x);
1126 format!("\"{cleaned}\"^^<http://www.w3.org/2001/XMLSchema#decimal>")
1127 }
1128}
1129
1130fn reverse(ast: PathAst) -> PathAst {
1132 match ast {
1133 PathAst::Pred(p, r) => PathAst::Pred(p, !r),
1134 PathAst::Rep(inner, rep) => PathAst::Rep(Box::new(reverse(*inner)), rep),
1135 PathAst::Seq(a, b) => PathAst::Seq(Box::new(reverse(*b)), Box::new(reverse(*a))),
1136 PathAst::Alt(a, b) => PathAst::Alt(Box::new(reverse(*a)), Box::new(reverse(*b))),
1137 PathAst::NegatedSet(s, r) => PathAst::NegatedSet(s, !r),
1138 }
1139}
1140
1141#[cfg(test)]
1142mod tests {
1143 use super::*;
1144 use crate::dictionary::DictionaryBuilder;
1145 use crate::index::GraphIndexBuilder;
1146 use crate::write_file_v05 as write_file;
1147
1148 fn rete_from(triples: &[(&str, &str, &str)]) -> Vec<u8> {
1149 let mut db = DictionaryBuilder::new();
1150 for (s, p, o) in triples {
1151 db.observe(s, p, o);
1152 }
1153 let dict = db.build();
1154 let mut ib = GraphIndexBuilder::new();
1155 for (s, p, o) in triples {
1156 ib.push(dict.encode(s, p, o).unwrap());
1157 }
1158 write_file(&dict, &ib.build(), false, &[], 0)
1159 }
1160
1161 #[test]
1162 fn parses_select_with_two_patterns() {
1163 let q = r#"
1164 PREFIX ex: <http://ex/>
1165 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }
1166 "#;
1167 let sel = parse_select(q).unwrap();
1168 assert_eq!(sel.project, vec!["x", "z"]);
1169 match &sel.plan {
1170 Plan::Bgp(p) => assert_eq!(p.len(), 2),
1171 other => panic!("expected a BGP plan, got {other:?}"),
1172 }
1173 }
1174
1175 #[test]
1176 fn evaluates_two_hop_select() {
1177 let bytes = rete_from(&[
1178 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1179 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1180 ]);
1181 let rete = Rete::open(&bytes).unwrap();
1182 let q = r#"PREFIX ex: <http://ex/>
1183 SELECT ?x ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }"#;
1184 let (proj, sols) = eval_sparql(&rete, q).unwrap();
1185 assert_eq!(proj, vec!["x", "z"]);
1186 assert_eq!(sols.len(), 1);
1187 assert_eq!(sols[0]["x"], "<http://ex/Alice>");
1188 assert_eq!(sols[0]["z"], "<http://ex/Carol>");
1189 }
1190
1191 #[test]
1192 fn union_returns_both_sides() {
1193 let bytes = rete_from(&[
1194 ("<http://ex/Alice>", "<http://ex/likes>", "<http://ex/Tea>"),
1195 ("<http://ex/Bob>", "<http://ex/hates>", "<http://ex/Tea>"),
1196 ]);
1197 let rete = Rete::open(&bytes).unwrap();
1198 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { \
1199 { ?p ex:likes ex:Tea } UNION { ?p ex:hates ex:Tea } }";
1200 let (_, sols) = eval_sparql(&rete, q).unwrap();
1201 let mut who: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
1202 who.sort();
1203 assert_eq!(who, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1204 }
1205
1206 #[test]
1207 fn optional_keeps_left_when_right_absent() {
1208 let bytes = rete_from(&[
1210 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1211 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1212 ("<http://ex/Alice>", "<http://ex/email>", "\"a@ex\""),
1213 ]);
1214 let rete = Rete::open(&bytes).unwrap();
1215 let q = "PREFIX ex: <http://ex/> SELECT ?p ?e WHERE { \
1216 ?p ex:name ?n . OPTIONAL { ?p ex:email ?e } }";
1217 let (_, sols) = eval_sparql(&rete, q).unwrap();
1218 assert_eq!(sols.len(), 2, "both people present");
1219 let alice = sols.iter().find(|b| b["p"] == "<http://ex/Alice>").unwrap();
1220 assert_eq!(alice["e"], "\"a@ex\"");
1221 let bob = sols.iter().find(|b| b["p"] == "<http://ex/Bob>").unwrap();
1222 assert!(!bob.contains_key("e"), "Bob has no email binding");
1223 }
1224
1225 #[test]
1226 fn numeric_filter_on_typed_literal() {
1227 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
1229 let bytes = rete_from(&[
1230 (
1231 "<http://ex/Alice>",
1232 "<http://ex/age>",
1233 &format!("\"30\"^^{xsd}"),
1234 ),
1235 (
1236 "<http://ex/Bob>",
1237 "<http://ex/age>",
1238 &format!("\"25\"^^{xsd}"),
1239 ),
1240 ]);
1241 let rete = Rete::open(&bytes).unwrap();
1242 let q = "PREFIX ex: <http://ex/> \
1243 SELECT ?p WHERE { ?p ex:age ?age . FILTER(?age > 27) }";
1244 let (_, sols) = eval_sparql(&rete, q).unwrap();
1245 assert_eq!(sols.len(), 1);
1246 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1247 }
1248
1249 #[test]
1250 fn filter_equality_and_boolean_logic() {
1251 let bytes = rete_from(&[
1252 ("<http://ex/Alice>", "<http://ex/city>", "<http://ex/NYC>"),
1253 ("<http://ex/Bob>", "<http://ex/city>", "<http://ex/LA>"),
1254 ("<http://ex/Carol>", "<http://ex/city>", "<http://ex/NYC>"),
1255 ]);
1256 let rete = Rete::open(&bytes).unwrap();
1257 let q = "PREFIX ex: <http://ex/> \
1258 SELECT ?p WHERE { ?p ex:city ?c . FILTER(?c = ex:NYC && ?p != ex:Carol) }";
1259 let (_, sols) = eval_sparql(&rete, q).unwrap();
1260 assert_eq!(sols.len(), 1);
1261 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1262 }
1263
1264 #[test]
1265 fn distinct_collapses_duplicate_projections() {
1266 let bytes = rete_from(&[
1268 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1269 (
1270 "<http://ex/Alice>",
1271 "<http://ex/knows>",
1272 "<http://ex/Carol>",
1273 ),
1274 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1275 ("<http://ex/Carol>", "<http://ex/knows>", "<http://ex/Dave>"),
1276 ]);
1277 let rete = Rete::open(&bytes).unwrap();
1278 let base = "PREFIX ex: <http://ex/> SELECT {} ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1279
1280 let (_, non_distinct) = eval_sparql(&rete, &base.replace("{}", "")).unwrap();
1281 assert_eq!(non_distinct.len(), 2); let (proj, distinct) = eval_sparql(&rete, &base.replace("{}", "DISTINCT")).unwrap();
1284 assert_eq!(proj, vec!["z"]);
1285 assert_eq!(distinct.len(), 1);
1286 assert_eq!(distinct[0]["z"], "<http://ex/Dave>");
1287 assert!(!distinct[0].contains_key("x"));
1289 }
1290
1291 #[test]
1292 fn transitive_path_reachability() {
1293 let bytes = rete_from(&[
1295 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
1296 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
1297 ("<http://ex/C>", "<http://ex/k>", "<http://ex/D>"),
1298 ]);
1299 let rete = Rete::open(&bytes).unwrap();
1300
1301 let q = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k+ ?y }";
1303 let (_, sols) = eval_sparql(&rete, q).unwrap();
1304 let mut ys: Vec<&str> = sols.iter().map(|b| b["y"].as_str()).collect();
1305 ys.sort();
1306 assert_eq!(ys, vec!["<http://ex/B>", "<http://ex/C>", "<http://ex/D>"]);
1307
1308 let q0 = "PREFIX ex: <http://ex/> SELECT ?y WHERE { ex:A ex:k* ?y }";
1310 let (_, s0) = eval_sparql(&rete, q0).unwrap();
1311 assert!(s0.iter().any(|b| b["y"] == "<http://ex/A>"));
1312 assert_eq!(s0.len(), 4); }
1314
1315 #[test]
1316 fn sequence_and_alternative_paths() {
1317 let bytes = rete_from(&[
1319 ("<http://ex/Alice>", "<http://ex/parent>", "<http://ex/Bob>"),
1320 ("<http://ex/Bob>", "<http://ex/parent>", "<http://ex/Carol>"),
1321 (
1322 "<http://ex/Alice>",
1323 "<http://ex/stepparent>",
1324 "<http://ex/Dave>",
1325 ),
1326 ]);
1327 let rete = Rete::open(&bytes).unwrap();
1328
1329 let q = "PREFIX ex: <http://ex/> SELECT ?g WHERE { ex:Alice ex:parent/ex:parent ?g }";
1331 let (_, sols) = eval_sparql(&rete, q).unwrap();
1332 assert_eq!(sols.len(), 1);
1333 assert_eq!(sols[0]["g"], "<http://ex/Carol>");
1334
1335 let qa = "PREFIX ex: <http://ex/> \
1337 SELECT ?p WHERE { ex:Alice ex:parent|ex:stepparent ?p }";
1338 let (_, sa) = eval_sparql(&rete, qa).unwrap();
1339 let mut ps: Vec<&str> = sa.iter().map(|b| b["p"].as_str()).collect();
1340 ps.sort();
1341 assert_eq!(ps, vec!["<http://ex/Bob>", "<http://ex/Dave>"]);
1342 }
1343
1344 #[test]
1345 fn group_concat_aggregate() {
1346 let bytes = rete_from(&[
1347 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1348 (
1349 "<http://ex/Alice>",
1350 "<http://ex/knows>",
1351 "<http://ex/Carol>",
1352 ),
1353 ]);
1354 let rete = Rete::open(&bytes).unwrap();
1355 let q = "PREFIX ex: <http://ex/> \
1356 SELECT (GROUP_CONCAT(?f; SEPARATOR=\"|\") AS ?fs) WHERE { ex:Alice ex:knows ?f }";
1357 let (_, sols) = eval_sparql(&rete, q).unwrap();
1358 assert_eq!(sols.len(), 1);
1359 let fs = sols[0]["fs"].trim_matches('"');
1361 let mut parts: Vec<&str> = fs.split('|').collect();
1362 parts.sort();
1363 assert_eq!(parts, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1364 }
1365
1366 #[test]
1367 fn group_by_having() {
1368 let bytes = rete_from(&[
1370 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1371 (
1372 "<http://ex/Alice>",
1373 "<http://ex/knows>",
1374 "<http://ex/Carol>",
1375 ),
1376 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1377 ]);
1378 let rete = Rete::open(&bytes).unwrap();
1379 let q = "PREFIX ex: <http://ex/> SELECT ?p (COUNT(?f) AS ?n) \
1381 WHERE { ?p ex:knows ?f } GROUP BY ?p HAVING (COUNT(?f) > 1)";
1382 let (_, sols) = eval_sparql(&rete, q).unwrap();
1383 assert_eq!(sols.len(), 1);
1384 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1385 assert_eq!(
1386 sols[0]["n"],
1387 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1388 );
1389 }
1390
1391 #[test]
1392 fn count_group_by() {
1393 let bytes = rete_from(&[
1395 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1396 (
1397 "<http://ex/Alice>",
1398 "<http://ex/knows>",
1399 "<http://ex/Carol>",
1400 ),
1401 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1402 ]);
1403 let rete = Rete::open(&bytes).unwrap();
1404 let q = "PREFIX ex: <http://ex/> \
1405 SELECT ?p (COUNT(?f) AS ?n) WHERE { ?p ex:knows ?f } GROUP BY ?p";
1406 let (_, sols) = eval_sparql(&rete, q).unwrap();
1407 let mut counts: Vec<(String, String)> = sols
1408 .iter()
1409 .map(|b| (b["p"].clone(), b["n"].clone()))
1410 .collect();
1411 counts.sort();
1412 assert_eq!(
1413 counts,
1414 vec![
1415 (
1416 "<http://ex/Alice>".into(),
1417 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1418 ),
1419 (
1420 "<http://ex/Bob>".into(),
1421 "\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>".into()
1422 ),
1423 ]
1424 );
1425 }
1426
1427 #[test]
1428 fn global_count_star() {
1429 let bytes = rete_from(&[
1430 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
1431 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
1432 ]);
1433 let rete = Rete::open(&bytes).unwrap();
1434 let (_, sols) = eval_sparql(&rete, "SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1435 assert_eq!(sols.len(), 1);
1436 assert_eq!(
1437 sols[0]["n"],
1438 "\"2\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1439 );
1440 }
1441
1442 #[test]
1443 fn summary_query_shape_classifies_only_exact_predicate_totals() {
1444 let count = summary_query_shape(
1445 "PREFIX ex: <http://ex/> SELECT (COUNT(*) AS ?n) WHERE { ?s ex:p ?o }",
1446 )
1447 .unwrap();
1448 assert_eq!(
1449 count,
1450 Some(SummaryQueryShape::PredicateCount {
1451 predicate: "<http://ex/p>".into(),
1452 variable: "n".into(),
1453 })
1454 );
1455
1456 let total = summary_query_shape("SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1457 assert_eq!(
1458 total,
1459 Some(SummaryQueryShape::TripleCount {
1460 variable: "n".into(),
1461 })
1462 );
1463
1464 let by_pred =
1465 summary_query_shape("SELECT ?p (COUNT(*) AS ?n) WHERE { ?s ?p ?o } GROUP BY ?p")
1466 .unwrap();
1467 assert_eq!(
1468 by_pred,
1469 Some(SummaryQueryShape::PredicateTotals {
1470 predicate_variable: "p".into(),
1471 count_variable: "n".into(),
1472 })
1473 );
1474
1475 let predicates = summary_query_shape("SELECT DISTINCT ?p WHERE { ?s ?p ?o }").unwrap();
1476 assert_eq!(
1477 predicates,
1478 Some(SummaryQueryShape::PredicateList {
1479 variable: "p".into(),
1480 })
1481 );
1482
1483 let predicate_count =
1484 summary_query_shape("SELECT (COUNT(DISTINCT ?p) AS ?n) WHERE { ?s ?p ?o }").unwrap();
1485 assert_eq!(
1486 predicate_count,
1487 Some(SummaryQueryShape::PredicateDistinctCount {
1488 variable: "n".into(),
1489 })
1490 );
1491
1492 let ask = summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o }").unwrap();
1493 assert_eq!(
1494 ask,
1495 Some(SummaryQueryShape::PredicateExists {
1496 predicate: "<http://ex/p>".into(),
1497 })
1498 );
1499
1500 let any_ask = summary_query_shape("ASK { ?s ?p ?o }").unwrap();
1501 assert_eq!(any_ask, Some(SummaryQueryShape::TripleExists));
1502
1503 let constrained =
1504 summary_query_shape("PREFIX ex: <http://ex/> ASK { ex:a ex:p ?o }").unwrap();
1505 assert_eq!(constrained, None);
1506
1507 let filtered =
1508 summary_query_shape("PREFIX ex: <http://ex/> ASK { ?s ex:p ?o FILTER(?s = ?o) }")
1509 .unwrap();
1510 assert_eq!(filtered, None);
1511 }
1512
1513 #[test]
1514 fn filter_exists_and_not_exists() {
1515 let bytes = rete_from(&[
1517 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1518 (
1519 "<http://ex/Alice>",
1520 "<http://ex/knows>",
1521 "<http://ex/Carol>",
1522 ),
1523 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Dave>"),
1524 ]);
1525 let rete = Rete::open(&bytes).unwrap();
1526
1527 let q_exists = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1529 ex:Alice ex:knows ?f . FILTER EXISTS { ?f ex:knows ?x } }";
1530 let (_, e) = eval_sparql(&rete, q_exists).unwrap();
1531 assert_eq!(
1532 e.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1533 vec!["<http://ex/Bob>"]
1534 );
1535
1536 let q_not = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1538 ex:Alice ex:knows ?f . FILTER NOT EXISTS { ?f ex:knows ?x } }";
1539 let (_, n) = eval_sparql(&rete, q_not).unwrap();
1540 assert_eq!(
1541 n.iter().map(|b| b["f"].as_str()).collect::<Vec<_>>(),
1542 vec!["<http://ex/Carol>"]
1543 );
1544 }
1545
1546 #[test]
1547 fn ask_repeated_variable_pattern() {
1548 let yes = rete_from(&[
1551 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1552 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Bob>"),
1553 ]);
1554 let rete = Rete::open(&yes).unwrap();
1555 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1556 QueryOutput::Ask(b) => assert!(b, "Bob knows himself"),
1557 other => panic!("expected Ask, got {other:?}"),
1558 }
1559 let no = rete_from(&[("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>")]);
1562 let rete = Rete::open(&no).unwrap();
1563 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?x ex:knows ?x }").unwrap() {
1564 QueryOutput::Ask(b) => assert!(!b, "nobody knows themselves"),
1565 other => panic!("expected Ask, got {other:?}"),
1566 }
1567 }
1568
1569 #[test]
1570 fn minus_excludes_compatible() {
1571 let bytes = rete_from(&[
1573 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1574 (
1575 "<http://ex/Alice>",
1576 "<http://ex/knows>",
1577 "<http://ex/Carol>",
1578 ),
1579 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1580 ]);
1581 let rete = Rete::open(&bytes).unwrap();
1582 let q = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1584 ex:Alice ex:knows ?f . MINUS { ?f ex:knows ?x } }";
1585 let (_, sols) = eval_sparql(&rete, q).unwrap();
1586 let fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1587 assert_eq!(fs, vec!["<http://ex/Carol>"]);
1588 }
1589
1590 #[test]
1591 fn filter_exists_disjoint_variable() {
1592 let bytes = rete_from(&[
1597 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1598 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1599 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1600 ]);
1601 let rete = Rete::open(&bytes).unwrap();
1602 let q_none = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1603 ?x ex:a ex:Person FILTER NOT EXISTS { ?y ex:a ex:Drink } }";
1604 assert!(eval_sparql(&rete, q_none).unwrap().1.is_empty());
1605 let q_all = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1606 ?x ex:a ex:Person FILTER EXISTS { ?y ex:a ex:Drink } }";
1607 assert_eq!(eval_sparql(&rete, q_all).unwrap().1.len(), 2);
1608 }
1609
1610 #[test]
1611 fn minus_disjoint_domain_keeps_all() {
1612 let bytes = rete_from(&[
1616 ("<http://ex/Alice>", "<http://ex/a>", "<http://ex/Person>"),
1617 ("<http://ex/Bob>", "<http://ex/a>", "<http://ex/Person>"),
1618 ("<http://ex/Tea>", "<http://ex/a>", "<http://ex/Drink>"),
1619 ]);
1620 let rete = Rete::open(&bytes).unwrap();
1621 let q = "PREFIX ex: <http://ex/> SELECT ?x WHERE { \
1622 ?x ex:a ex:Person MINUS { ?y ex:a ex:Drink } }";
1623 let (_, sols) = eval_sparql(&rete, q).unwrap();
1624 let mut xs: Vec<&str> = sols.iter().map(|b| b["x"].as_str()).collect();
1625 xs.sort();
1626 assert_eq!(xs, vec!["<http://ex/Alice>", "<http://ex/Bob>"]);
1627 }
1628
1629 #[test]
1630 fn values_pushdown_selects_subset() {
1631 let bytes = rete_from(&[
1634 ("<http://ex/a>", "<http://ex/d>", "<http://ex/Bio>"),
1635 ("<http://ex/b>", "<http://ex/d>", "<http://ex/Phys>"),
1636 ("<http://ex/c>", "<http://ex/d>", "<http://ex/Chem>"),
1637 ]);
1638 let rete = Rete::open(&bytes).unwrap();
1639 let q = "PREFIX ex: <http://ex/> SELECT ?p ?disc WHERE { \
1640 VALUES ?disc { ex:Bio ex:Phys } ?p ex:d ?disc }";
1641 let (_, sols) = eval_sparql(&rete, q).unwrap();
1642 let mut got: Vec<(String, String)> = sols
1643 .iter()
1644 .map(|b| (b["p"].clone(), b["disc"].clone()))
1645 .collect();
1646 got.sort();
1647 assert_eq!(
1648 got,
1649 vec![
1650 ("<http://ex/a>".into(), "<http://ex/Bio>".into()),
1651 ("<http://ex/b>".into(), "<http://ex/Phys>".into()),
1652 ]
1653 );
1654 }
1655
1656 #[test]
1657 fn values_inline_data_joins() {
1658 let bytes = rete_from(&[
1659 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1660 (
1661 "<http://ex/Alice>",
1662 "<http://ex/knows>",
1663 "<http://ex/Carol>",
1664 ),
1665 ("<http://ex/Dave>", "<http://ex/knows>", "<http://ex/Eve>"),
1666 ]);
1667 let rete = Rete::open(&bytes).unwrap();
1668 let q = "PREFIX ex: <http://ex/> \
1670 SELECT ?f WHERE { VALUES ?p { ex:Alice } ?p ex:knows ?f }";
1671 let (_, sols) = eval_sparql(&rete, q).unwrap();
1672 let mut fs: Vec<&str> = sols.iter().map(|b| b["f"].as_str()).collect();
1673 fs.sort();
1674 assert_eq!(fs, vec!["<http://ex/Bob>", "<http://ex/Carol>"]);
1675 }
1676
1677 #[test]
1678 fn graph_queries_over_named_graphs() {
1679 use crate::write_dataset_v05 as write_dataset;
1680 let mut db = DictionaryBuilder::new();
1682 let edges = [
1683 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1684 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1685 ];
1686 for (s, p, o) in edges {
1687 db.observe(s, p, o);
1688 }
1689 let dict = db.build();
1690 let mut social = GraphIndexBuilder::new();
1691 social.push(dict.encode(edges[0].0, edges[0].1, edges[0].2).unwrap());
1692 let mut profile = GraphIndexBuilder::new();
1693 profile.push(dict.encode(edges[1].0, edges[1].1, edges[1].2).unwrap());
1694 let named = vec![
1695 ("<http://ex/social>".to_string(), social.build()),
1696 ("<http://ex/profile>".to_string(), profile.build()),
1697 ];
1698 let bytes = write_dataset(
1699 &dict,
1700 &GraphIndexBuilder::new().build(),
1701 &named,
1702 true,
1703 &[],
1704 0,
1705 );
1706 let rete = Rete::open(&bytes).unwrap();
1707
1708 let q = "PREFIX ex: <http://ex/> \
1710 SELECT ?f WHERE { GRAPH ex:social { ex:Alice ex:knows ?f } }";
1711 let (_, s) = eval_sparql(&rete, q).unwrap();
1712 assert_eq!(s.len(), 1);
1713 assert_eq!(s[0]["f"], "<http://ex/Bob>");
1714
1715 let q2 = "PREFIX ex: <http://ex/> \
1717 SELECT ?g WHERE { GRAPH ?g { ex:Bob ex:age ?a } }";
1718 let (_, s2) = eval_sparql(&rete, q2).unwrap();
1719 assert_eq!(s2.len(), 1);
1720 assert_eq!(s2[0]["g"], "<http://ex/profile>");
1721
1722 let q3 = "PREFIX ex: <http://ex/> SELECT ?f WHERE { \
1725 GRAPH ex:profile { ?f ex:age ?a . FILTER EXISTS { ?f ex:age ?a2 } } }";
1726 assert_eq!(eval_sparql(&rete, q3).unwrap().1.len(), 1);
1727 let q4 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
1728 GRAPH ex:social { ?s ex:knows ?o . FILTER NOT EXISTS { ?s ex:age ?a } } }";
1729 assert_eq!(eval_sparql(&rete, q4).unwrap().1.len(), 1);
1731 }
1732
1733 #[test]
1734 fn from_unions_named_graphs() {
1735 use crate::write_dataset_v05 as write_dataset;
1736 let mut db = DictionaryBuilder::new();
1739 let t = [
1740 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1741 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1742 ];
1743 for (s, p, o) in t {
1744 db.observe(s, p, o);
1745 }
1746 let dict = db.build();
1747 let mut g1 = GraphIndexBuilder::new();
1748 g1.push(dict.encode(t[0].0, t[0].1, t[0].2).unwrap());
1749 let mut g2 = GraphIndexBuilder::new();
1750 g2.push(dict.encode(t[1].0, t[1].1, t[1].2).unwrap());
1751 let named = vec![
1752 ("<http://ex/social>".to_string(), g1.build()),
1753 ("<http://ex/profile>".to_string(), g2.build()),
1754 ];
1755 let bytes = write_dataset(
1756 &dict,
1757 &GraphIndexBuilder::new().build(),
1758 &named,
1759 true,
1760 &[],
1761 0,
1762 );
1763 let rete = Rete::open(&bytes).unwrap();
1764
1765 let q0 = "PREFIX ex: <http://ex/> SELECT ?z WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1767 assert!(eval_sparql(&rete, q0).unwrap().1.is_empty());
1768
1769 let q = "PREFIX ex: <http://ex/> \
1771 SELECT ?z FROM ex:social FROM ex:profile \
1772 WHERE { ?x ex:knows ?y . ?y ex:knows ?z }";
1773 let (_, s) = eval_sparql(&rete, q).unwrap();
1774 assert_eq!(s.len(), 1);
1775 assert_eq!(s[0]["z"], "<http://ex/Carol>");
1776
1777 let qn = "PREFIX ex: <http://ex/> SELECT ?g FROM NAMED ex:social \
1779 WHERE { GRAPH ?g { ?x ex:knows ?y } }";
1780 let (_, sn) = eval_sparql(&rete, qn).unwrap();
1781 let gs: Vec<&str> = sn.iter().map(|b| b["g"].as_str()).collect();
1782 assert_eq!(gs, vec!["<http://ex/social>"]); }
1784
1785 #[test]
1786 fn union_default_graph_opt_in() {
1787 use crate::write_dataset_v05 as write_dataset;
1788 let mut db = DictionaryBuilder::new();
1792 let t = [
1793 ("<http://ex/Root>", "<http://ex/knows>", "<http://ex/Alice>"),
1794 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1795 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1796 ];
1797 for (s, p, o) in t {
1798 db.observe(s, p, o);
1799 }
1800 let dict = db.build();
1801 let enc = |i: usize| dict.encode(t[i].0, t[i].1, t[i].2).unwrap();
1802 let mut def = GraphIndexBuilder::new();
1803 def.push(enc(0));
1804 let mut g1 = GraphIndexBuilder::new();
1805 g1.push(enc(1));
1806 let mut g2 = GraphIndexBuilder::new();
1807 g2.push(enc(1)); g2.push(enc(2));
1809 let named = vec![
1810 ("<http://ex/social>".to_string(), g1.build()),
1811 ("<http://ex/profile>".to_string(), g2.build()),
1812 ];
1813 let bytes = write_dataset(&dict, &def.build(), &named, true, &[], 0);
1814 let rete = Rete::open(&bytes).unwrap();
1815 let union = QueryOpts {
1816 union_default_graph: true,
1817 ..QueryOpts::default()
1818 };
1819 let rows = |out: QueryOutput| match out {
1820 QueryOutput::Select(_, rows) => rows,
1821 other => panic!("expected Select, got {other:?}"),
1822 };
1823
1824 let q = "PREFIX ex: <http://ex/> SELECT ?s ?o WHERE { ?s ex:knows ?o }";
1828 assert_eq!(rows(eval_query(&rete, q).unwrap()).len(), 1);
1829
1830 let got = rows(eval_query_with(&rete, q, union).unwrap());
1834 assert_eq!(got.len(), 3);
1835 assert!(got.iter().any(|b| b["s"] == "<http://ex/Root>"));
1836
1837 let qj = "PREFIX ex: <http://ex/> SELECT ?z WHERE { \
1839 ex:Root ex:knows ?x . ?x ex:knows ?y . ?y ex:knows ?z }";
1840 let sj = rows(eval_query_with(&rete, qj, union).unwrap());
1841 assert_eq!(sj.len(), 1);
1842 assert_eq!(sj[0]["z"], "<http://ex/Carol>");
1843
1844 let qg = "PREFIX ex: <http://ex/> SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ex:knows ?o } }";
1846 assert_eq!(rows(eval_query_with(&rete, qg, union).unwrap()).len(), 2);
1847
1848 let qf = "PREFIX ex: <http://ex/> SELECT ?s ?o FROM ex:social WHERE { ?s ex:knows ?o }";
1851 let sf = rows(eval_query_with(&rete, qf, union).unwrap());
1852 assert_eq!(sf.len(), 1);
1853 assert_eq!(sf[0]["s"], "<http://ex/Alice>");
1854
1855 let mut db2 = DictionaryBuilder::new();
1858 db2.observe(t[1].0, t[1].1, t[1].2);
1859 let dict2 = db2.build();
1860 let mut only = GraphIndexBuilder::new();
1861 only.push(dict2.encode(t[1].0, t[1].1, t[1].2).unwrap());
1862 let named2 = vec![("<http://ex/only>".to_string(), only.build())];
1863 let bytes2 = write_dataset(
1864 &dict2,
1865 &GraphIndexBuilder::new().build(),
1866 &named2,
1867 true,
1868 &[],
1869 0,
1870 );
1871 let rete2 = Rete::open(&bytes2).unwrap();
1872 assert_eq!(rows(eval_query(&rete2, q).unwrap()).len(), 0); assert_eq!(rows(eval_query_with(&rete2, q, union).unwrap()).len(), 1);
1874 }
1875
1876 #[test]
1877 fn describe_resource() {
1878 let bytes = rete_from(&[
1879 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1880 ("<http://ex/Alice>", "<http://ex/age>", "\"30\""),
1881 ("<http://ex/Bob>", "<http://ex/age>", "\"25\""),
1882 ]);
1883 let rete = Rete::open(&bytes).unwrap();
1884 match eval_query(&rete, "DESCRIBE <http://ex/Alice>").unwrap() {
1886 QueryOutput::Construct(t) => assert_eq!(t.len(), 2),
1887 other => panic!("expected Construct, got {other:?}"),
1888 }
1889 let q = "PREFIX ex: <http://ex/> DESCRIBE ?x WHERE { ?x ex:age ?a }";
1891 match eval_query(&rete, q).unwrap() {
1892 QueryOutput::Construct(t) => {
1893 assert_eq!(t.len(), 3);
1895 }
1896 other => panic!("expected Construct, got {other:?}"),
1897 }
1898 }
1899
1900 #[test]
1901 fn ask_and_construct() {
1902 let bytes = rete_from(&[
1903 ("<http://ex/Alice>", "<http://ex/knows>", "<http://ex/Bob>"),
1904 ("<http://ex/Bob>", "<http://ex/knows>", "<http://ex/Carol>"),
1905 ]);
1906 let rete = Rete::open(&bytes).unwrap();
1907
1908 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:knows ?b }").unwrap() {
1910 QueryOutput::Ask(b) => assert!(b),
1911 other => panic!("expected Ask, got {other:?}"),
1912 }
1913 match eval_query(&rete, "PREFIX ex: <http://ex/> ASK { ?a ex:likes ?b }").unwrap() {
1914 QueryOutput::Ask(b) => assert!(!b),
1915 other => panic!("expected Ask, got {other:?}"),
1916 }
1917
1918 let q = "PREFIX ex: <http://ex/> \
1920 CONSTRUCT { ?b ex:knownBy ?a } WHERE { ?a ex:knows ?b }";
1921 match eval_query(&rete, q).unwrap() {
1922 QueryOutput::Construct(mut triples) => {
1923 triples.sort();
1924 assert_eq!(triples.len(), 2);
1925 assert!(triples.contains(&(
1926 "<http://ex/Bob>".into(),
1927 "<http://ex/knownBy>".into(),
1928 "<http://ex/Alice>".into(),
1929 )));
1930 }
1931 other => panic!("expected Construct, got {other:?}"),
1932 }
1933 }
1934
1935 #[test]
1936 fn substr_strbefore_strafter() {
1937 let bytes = rete_from(&[("<http://ex/a>", "<http://ex/name>", "\"Alice Smith\"")]);
1938 let rete = Rete::open(&bytes).unwrap();
1939 let q = "PREFIX ex: <http://ex/> SELECT ?first ?last ?ini WHERE { \
1940 ?p ex:name ?n . \
1941 BIND(STRBEFORE(?n, \" \") AS ?first) \
1942 BIND(STRAFTER(?n, \" \") AS ?last) \
1943 BIND(SUBSTR(?n, 1, 1) AS ?ini) }";
1944 let (_, sols) = eval_sparql(&rete, q).unwrap();
1945 assert_eq!(sols[0]["first"], "\"Alice\"");
1947 assert_eq!(sols[0]["last"], "\"Smith\"");
1948 assert_eq!(sols[0]["ini"], "\"A\"");
1949 }
1950
1951 #[test]
1952 fn concat_and_coalesce() {
1953 let bytes = rete_from(&[
1955 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice\""),
1956 ("<http://ex/Alice>", "<http://ex/nick>", "\"Al\""),
1957 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob\""),
1958 ]);
1959 let rete = Rete::open(&bytes).unwrap();
1960 let q = "PREFIX ex: <http://ex/> SELECT ?label WHERE { \
1962 ?p ex:name ?name . OPTIONAL { ?p ex:nick ?nick } \
1963 BIND(CONCAT(\"@\", COALESCE(?nick, ?name)) AS ?label) }";
1964 let (_, sols) = eval_sparql(&rete, q).unwrap();
1965 let mut labels: Vec<&str> = sols.iter().map(|b| b["label"].as_str()).collect();
1966 labels.sort();
1967 assert_eq!(labels, vec!["\"@Al\"", "\"@Bob\""]);
1968 }
1969
1970 #[test]
1971 fn builtin_functions() {
1972 let bytes = rete_from(&[
1973 ("<http://ex/Alice>", "<http://ex/name>", "\"Alice Smith\""),
1974 ("<http://ex/Bob>", "<http://ex/name>", "\"Bob Jones\""),
1975 ]);
1976 let rete = Rete::open(&bytes).unwrap();
1977 let q = "PREFIX ex: <http://ex/> SELECT ?p ?len WHERE { \
1979 ?p ex:name ?n . FILTER(CONTAINS(?n, \"Smith\")) BIND(STRLEN(?n) AS ?len) }";
1980 let (_, sols) = eval_sparql(&rete, q).unwrap();
1981 assert_eq!(sols.len(), 1);
1982 assert_eq!(sols[0]["p"], "<http://ex/Alice>");
1983 assert_eq!(
1984 sols[0]["len"],
1985 "\"11\"^^<http://www.w3.org/2001/XMLSchema#integer>"
1986 ); }
1988
1989 #[test]
1990 fn geosparql_filter_and_functions() {
1991 let wkt = "\"POLYGON((0 0,10 0,10 10,0 10,0 0))\"^^\
1992 <http://www.opengis.net/ont/geosparql#wktLiteral>";
1993 let bytes = rete_from(&[
1994 (
1995 "<http://ex/f>",
1996 "<http://www.opengis.net/ont/geosparql#hasGeometry>",
1997 "<http://ex/f/g>",
1998 ),
1999 (
2000 "<http://ex/f/g>",
2001 "<http://www.opengis.net/ont/geosparql#asWKT>",
2002 wkt,
2003 ),
2004 (
2005 "<http://ex/f>",
2006 "<http://ex/year>",
2007 "\"1815\"^^<http://www.w3.org/2001/XMLSchema#integer>",
2008 ),
2009 ]);
2010 let rete = Rete::open(&bytes).unwrap();
2011 let pre = "PREFIX geo: <http://www.opengis.net/ont/geosparql#> \
2012 PREFIX geof: <http://www.opengis.net/def/function/geosparql/> \
2013 PREFIX uom: <http://www.opengis.net/def/uom/OGC/1.0/> PREFIX ex: <http://ex/> ";
2014
2015 let (_, s) = eval_sparql(
2017 &rete,
2018 &format!(
2019 "{pre}SELECT ?f WHERE {{ ?f ex:year ?y ; \
2020 geo:hasGeometry/geo:asWKT ?w . \
2021 FILTER(?y = 1815 && geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral)) }}"
2022 ),
2023 )
2024 .unwrap();
2025 assert_eq!(s.len(), 1, "point inside the polygon in year 1815");
2026 assert_eq!(s[0]["f"], "<http://ex/f>");
2027
2028 let (_, s) = eval_sparql(
2030 &rete,
2031 &format!(
2032 "{pre}SELECT ?f WHERE {{ \
2033 ?f geo:hasGeometry/geo:asWKT ?w . \
2034 FILTER(geof:sfContains(?w, \"POINT(50 50)\"^^geo:wktLiteral)) }}"
2035 ),
2036 )
2037 .unwrap();
2038 assert!(s.is_empty());
2039
2040 let (_, s) = eval_sparql(
2042 &rete,
2043 &format!(
2044 "{pre}SELECT ?f WHERE {{ \
2045 ?f geo:hasGeometry/geo:asWKT ?w . \
2046 FILTER(geof:sfWithin(\"POINT(5 5)\"^^geo:wktLiteral, ?w)) }}"
2047 ),
2048 )
2049 .unwrap();
2050 assert_eq!(s.len(), 1);
2051
2052 let (_, s) = eval_sparql(
2054 &rete,
2055 &format!(
2056 "{pre}SELECT ?f WHERE {{ \
2057 ?f geo:hasGeometry/geo:asWKT ?w . \
2058 FILTER(geof:sfContains(?w, \"garbage\"^^geo:wktLiteral)) }}"
2059 ),
2060 )
2061 .unwrap();
2062 assert!(s.is_empty());
2063
2064 let (_, s) = eval_sparql(
2066 &rete,
2067 &format!(
2068 "{pre}SELECT ?hit WHERE {{ \
2069 ?f geo:hasGeometry/geo:asWKT ?w . \
2070 BIND(geof:sfContains(?w, \"POINT(5 5)\"^^geo:wktLiteral) AS ?hit) }}"
2071 ),
2072 )
2073 .unwrap();
2074 assert_eq!(
2075 s[0]["hit"],
2076 "\"true\"^^<http://www.w3.org/2001/XMLSchema#boolean>"
2077 );
2078
2079 let (_, s) = eval_sparql(
2081 &rete,
2082 &format!(
2083 "{pre}SELECT ?d WHERE {{ BIND(geof:distance(\
2084 \"POINT(0 0)\"^^geo:wktLiteral, \"POINT(0 1)\"^^geo:wktLiteral, uom:metre) AS ?d) }}"
2085 ),
2086 )
2087 .unwrap();
2088 assert!(
2089 s[0]["d"].ends_with("XMLSchema#double>"),
2090 "distance is xsd:double: {}",
2091 s[0]["d"]
2092 );
2093 let (_, s) = eval_sparql(
2094 &rete,
2095 &format!(
2096 "{pre}SELECT ?e WHERE {{ \
2097 ?f geo:hasGeometry/geo:asWKT ?w . BIND(geof:envelope(?w) AS ?e) }}"
2098 ),
2099 )
2100 .unwrap();
2101 assert!(s[0]["e"].contains("wktLiteral") && s[0]["e"].contains("POLYGON"));
2102
2103 assert!(eval_sparql(
2105 &rete,
2106 &format!(
2107 "{pre}SELECT ?x WHERE {{ \
2108 BIND(geof:buffer(\"POINT(0 0)\"^^geo:wktLiteral, 1) AS ?x) }}"
2109 )
2110 )
2111 .is_err());
2112 }
2113
2114 #[test]
2115 fn bind_arithmetic() {
2116 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2117 let bytes = rete_from(&[(
2118 "<http://ex/a>",
2119 "<http://ex/age>",
2120 &format!("\"30\"^^{xsd}"),
2121 )]);
2122 let rete = Rete::open(&bytes).unwrap();
2123 let q = "PREFIX ex: <http://ex/> \
2125 SELECT ?next WHERE { ?p ex:age ?age . BIND(?age + 1 AS ?next) FILTER(?age * 2 > 50) }";
2126 let (_, sols) = eval_sparql(&rete, q).unwrap();
2127 assert_eq!(sols.len(), 1);
2128 assert_eq!(
2129 sols[0]["next"],
2130 "\"31\"^^<http://www.w3.org/2001/XMLSchema#integer>"
2131 );
2132 }
2133
2134 #[test]
2135 fn bind_value_is_visible_to_a_following_filter_and_join() {
2136 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2140 let n = |v: i32| format!("\"{v}\"^^{xsd}");
2141 let bytes = rete_from(&[
2142 ("<http://ex/a>", "<http://ex/v>", &n(1)),
2143 ("<http://ex/b>", "<http://ex/v>", &n(2)),
2144 ("<http://ex/c>", "<http://ex/v>", &n(3)),
2145 ("<http://ex/x>", "<http://ex/v>", &n(3)),
2147 ]);
2148 let rete = Rete::open(&bytes).unwrap();
2149
2150 let q1 = "PREFIX ex: <http://ex/> SELECT ?s WHERE { \
2152 ?s ex:v ?o . BIND(?o + 1 AS ?z) FILTER(?z = 3) }";
2153 let (_, s1) = eval_sparql(&rete, q1).unwrap();
2154 assert_eq!(s1.len(), 1, "only b (2+1=3) passes");
2155 assert_eq!(s1[0]["s"], "<http://ex/b>");
2156
2157 let q2 = "PREFIX ex: <http://ex/> SELECT ?s ?s2 WHERE { \
2159 ?s ex:v ?o . BIND(?o + 1 AS ?z) ?s2 ex:v ?z }";
2160 let (_, s2) = eval_sparql(&rete, q2).unwrap();
2161 let mut pairs: Vec<(String, String)> = s2
2163 .iter()
2164 .map(|b| (b["s"].clone(), b["s2"].clone()))
2165 .collect();
2166 pairs.sort();
2167 assert_eq!(
2168 pairs,
2169 vec![
2170 ("<http://ex/a>".to_string(), "<http://ex/b>".to_string()),
2171 ("<http://ex/b>".to_string(), "<http://ex/c>".to_string()),
2172 ("<http://ex/b>".to_string(), "<http://ex/x>".to_string()),
2173 ]
2174 );
2175 }
2176
2177 #[test]
2178 fn order_by_numeric_desc_then_limit() {
2179 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2180 let bytes = rete_from(&[
2181 (
2182 "<http://ex/a>",
2183 "<http://ex/age>",
2184 &format!("\"30\"^^{xsd}"),
2185 ),
2186 (
2187 "<http://ex/b>",
2188 "<http://ex/age>",
2189 &format!("\"25\"^^{xsd}"),
2190 ),
2191 (
2192 "<http://ex/c>",
2193 "<http://ex/age>",
2194 &format!("\"40\"^^{xsd}"),
2195 ),
2196 ]);
2197 let rete = Rete::open(&bytes).unwrap();
2198 let q = "PREFIX ex: <http://ex/> \
2200 SELECT ?p WHERE { ?p ex:age ?age } ORDER BY DESC(?age) LIMIT 2";
2201 let (_, sols) = eval_sparql(&rete, q).unwrap();
2202 let ps: Vec<&str> = sols.iter().map(|b| b["p"].as_str()).collect();
2203 assert_eq!(ps, vec!["<http://ex/c>", "<http://ex/a>"]); }
2205
2206 #[test]
2207 fn limit_early_out_two_hop_join() {
2208 let bytes = rete_from(&[
2212 ("<http://ex/A>", "<http://ex/k>", "<http://ex/B>"),
2213 ("<http://ex/B>", "<http://ex/k>", "<http://ex/C>"),
2214 ("<http://ex/B>", "<http://ex/k>", "<http://ex/D>"),
2215 ("<http://ex/C>", "<http://ex/k>", "<http://ex/E>"),
2216 ]);
2217 let rete = Rete::open(&bytes).unwrap();
2218 let q = "PREFIX ex: <http://ex/> SELECT ?x ?z WHERE { ?x ex:k ?y . ?y ex:k ?z }";
2219 let (_, full) = eval_sparql(&rete, q).unwrap();
2220 assert_eq!(full.len(), 3);
2221
2222 let (_, one) = eval_sparql(&rete, &format!("{q} LIMIT 1")).unwrap();
2223 assert_eq!(one.len(), 1);
2224 assert!(one.iter().all(|r| full.contains(r)));
2226
2227 let (_, all) = eval_sparql(&rete, &format!("{q} LIMIT 100")).unwrap();
2229 assert_eq!(all.len(), 3);
2230 let (_, off) = eval_sparql(&rete, &format!("{q} LIMIT 100 OFFSET 2")).unwrap();
2231 assert_eq!(off.len(), 1);
2232 }
2233
2234 #[test]
2235 fn limit_early_out_filter_over_bgp() {
2236 let xsd = "<http://www.w3.org/2001/XMLSchema#integer>";
2239 let bytes = rete_from(&[
2240 ("<http://ex/a>", "<http://ex/n>", &format!("\"10\"^^{xsd}")),
2241 ("<http://ex/b>", "<http://ex/n>", &format!("\"20\"^^{xsd}")),
2242 ("<http://ex/c>", "<http://ex/n>", &format!("\"30\"^^{xsd}")),
2243 ("<http://ex/d>", "<http://ex/n>", &format!("\"40\"^^{xsd}")),
2244 ]);
2245 let rete = Rete::open(&bytes).unwrap();
2246 let q = "PREFIX ex: <http://ex/> SELECT ?p WHERE { ?p ex:n ?v FILTER(?v > 15) }";
2247 let (_, full) = eval_sparql(&rete, q).unwrap();
2248 assert_eq!(full.len(), 3); let (_, two) = eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap();
2250 assert_eq!(two.len(), 2);
2251 assert!(two.iter().all(|r| full.contains(r)));
2252 }
2253
2254 #[test]
2255 fn distinct_bgp_fast_matches_general() {
2256 let bytes = rete_from(&[
2259 ("<http://ex/a>", "<http://ex/p>", "<http://ex/x>"),
2260 ("<http://ex/b>", "<http://ex/p>", "<http://ex/x>"),
2261 ("<http://ex/b>", "<http://ex/p>", "<http://ex/y>"),
2262 ("<http://ex/c>", "<http://ex/p>", "<http://ex/z>"),
2263 ]);
2264 let rete = Rete::open(&bytes).unwrap();
2265 let q = "PREFIX ex: <http://ex/> SELECT DISTINCT ?o WHERE { ?s ex:p ?o }";
2267 let (proj, sols) = eval_sparql(&rete, q).unwrap();
2268 assert_eq!(proj, vec!["o"]);
2269 let mut os: Vec<&str> = sols.iter().map(|b| b["o"].as_str()).collect();
2270 os.sort();
2271 assert_eq!(os, vec!["<http://ex/x>", "<http://ex/y>", "<http://ex/z>"]);
2272 assert_eq!(
2274 eval_sparql(&rete, &format!("{q} LIMIT 2")).unwrap().1.len(),
2275 2
2276 );
2277 let q2 = "PREFIX ex: <http://ex/> SELECT DISTINCT ?s ?o WHERE { ?s ex:p ?o }";
2279 assert_eq!(eval_sparql(&rete, q2).unwrap().1.len(), 4);
2280 }
2281
2282 #[test]
2283 fn limit_caps_solutions() {
2284 let bytes = rete_from(&[
2285 ("<http://ex/a>", "<http://ex/p>", "<http://ex/1>"),
2286 ("<http://ex/b>", "<http://ex/p>", "<http://ex/2>"),
2287 ("<http://ex/c>", "<http://ex/p>", "<http://ex/3>"),
2288 ]);
2289 let rete = Rete::open(&bytes).unwrap();
2290 let (_, sols) =
2291 eval_sparql(&rete, "SELECT ?x WHERE { ?x <http://ex/p> ?y } LIMIT 2").unwrap();
2292 assert_eq!(sols.len(), 2);
2293 }
2294
2295 #[test]
2302 fn string_filters_see_past_an_embedded_quote() {
2303 let bytes = rete_from(&[
2304 (
2305 "<http://ex/a>",
2306 "<http://ex/label>",
2307 "\"plain Quantum here\"",
2308 ),
2309 (
2310 "<http://ex/b>",
2311 "<http://ex/label>",
2312 r#""He said \"Quantum\" loudly"@en"#,
2313 ),
2314 (
2315 "<http://ex/c>",
2316 "<http://ex/label>",
2317 r#""Theory of \"Quantum\" Gases""#,
2318 ),
2319 ("<http://ex/d>", "<http://ex/label>", "\"no match at all\""),
2320 (
2321 "<http://ex/e>",
2322 "<http://ex/label>",
2323 "\"tab\\there\\nline two\"^^<http://www.w3.org/2001/XMLSchema#string>",
2324 ),
2325 ]);
2326 let rete = Rete::open(&bytes).unwrap();
2327 let subjects = |filter: &str| {
2328 let q = format!("SELECT ?s WHERE {{ ?s <http://ex/label> ?l FILTER({filter}) }}");
2329 let (_, sols) = eval_sparql(&rete, &q).unwrap();
2330 let mut out: Vec<String> = sols.iter().map(|b| b["s"].clone()).collect();
2331 out.sort();
2332 out
2333 };
2334 let (a, b, c, e) = (
2335 "<http://ex/a>",
2336 "<http://ex/b>",
2337 "<http://ex/c>",
2338 "<http://ex/e>",
2339 );
2340 assert_eq!(subjects(r#"CONTAINS(?l, "Quantum")"#), vec![a, b, c]);
2341 assert_eq!(subjects(r#"CONTAINS(?l, "Gases")"#), vec![c]);
2342 assert_eq!(subjects(r#"STRENDS(?l, "loudly")"#), vec![b]);
2343 assert_eq!(subjects(r#"STRSTARTS(?l, "Theory of \"Q")"#), vec![c]);
2344 assert_eq!(subjects(r#"REGEX(?l, "gases$", "i")"#), vec![c]);
2345 assert_eq!(subjects(r#"REGEX(?l, "^line two$", "m")"#), vec![e]);
2346 assert_eq!(subjects(r#"CONTAINS(?l, "\there")"#), vec![e]);
2347 assert_eq!(subjects(r#"LANGMATCHES(LANG(?l), "en")"#), vec![b]);
2348 assert_eq!(subjects(r#"STRAFTER(?l, "Quantum") = "\" Gases""#), vec![c]);
2349 assert_eq!(
2350 subjects(r#"STRBEFORE(?l, " loudly") = "He said \"Quantum\""@en"#),
2351 vec![b]
2352 );
2353 assert_eq!(subjects(r#"STRLEN(?l) = 25"#), vec![c]);
2354 assert_eq!(subjects(r#"STRLEN(?l) = 24"#), vec![b]);
2355 }
2356
2357 #[test]
2365 fn reasoning_rewrites_only_the_atoms_it_can_extend() {
2366 let dom = "<http://www.w3.org/2000/01/rdf-schema#domain>";
2367 let sub = "<http://www.w3.org/2000/01/rdf-schema#subClassOf>";
2368 let ty = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
2369 let bytes = rete_from(&[
2370 (
2371 "<http://ex/hasCharacterisation>",
2372 dom,
2373 "<http://ex/Citation>",
2374 ),
2375 ("<http://ex/SelfCitation>", sub, "<http://ex/Citation>"),
2376 ("<http://ex/writtenBy>", dom, "<http://ex/Work>"),
2377 ("<http://ex/r1>", ty, "<http://ex/Role>"),
2378 ("<http://ex/w1>", ty, "<http://ex/Work>"),
2379 ("<http://ex/w2>", "<http://ex/writtenBy>", "<http://ex/ann>"),
2380 ]);
2381 let rete = Rete::open(&bytes).unwrap();
2382 let rewrite = |q: &str| {
2383 let sel = parse_select(q).unwrap();
2384 ql::reason_rewrite(sel.plan, &rete, &sel.project)
2385 };
2386 assert!(matches!(
2388 rewrite("SELECT ?s WHERE { ?s a <http://ex/Role> ; ?p ?o }"),
2389 Plan::Bgp(p) if p.len() == 2
2390 ));
2391 assert!(matches!(
2394 rewrite("SELECT ?s WHERE { ?s a <http://ex/Citation> }"),
2395 Plan::Bgp(p) if p.len() == 1
2396 ));
2397 match rewrite("SELECT ?s WHERE { ?s a <http://ex/Work> }") {
2399 Plan::DistinctUnion { vars, sources } => {
2400 assert_eq!(vars, vec!["s".to_string()]);
2401 assert_eq!(sources.len(), 2);
2402 }
2403 other => panic!("expected a DistinctUnion, got {other:?}"),
2404 }
2405 let (_, sols) =
2406 eval_sparql_reasoned(&rete, "SELECT ?s WHERE { ?s a <http://ex/Work> }").unwrap();
2407 let mut got: Vec<&str> = sols.iter().map(|b| b["s"].as_str()).collect();
2408 got.sort();
2409 assert_eq!(got, vec!["<http://ex/w1>", "<http://ex/w2>"]);
2410 }
2411
2412 #[test]
2417 fn a_small_left_side_probes_the_right_bgp() {
2418 let mut t: Vec<(String, String, String)> = Vec::new();
2419 let e = |n: String| format!("<http://ex/{n}>");
2420 for i in 0..200 {
2421 t.push((e(format!("j{i}")), e("a".into()), e("Journal".into())));
2422 t.push((e(format!("j{i}")), e("title".into()), format!("\"T{i}\"")));
2423 t.push((e(format!("j{i}")), e("id".into()), e(format!("id{i}"))));
2424 t.push((e(format!("id{i}")), e("value".into()), format!("\"{i}\"")));
2425 }
2426 t.push((e("work".into()), e("partOf".into()), e("issue".into())));
2427 t.push((e("issue".into()), e("partOf".into()), e("j7".into())));
2428 let refs: Vec<(&str, &str, &str)> = t
2429 .iter()
2430 .map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
2431 .collect();
2432 let rete = Rete::open(&rete_from(&refs)).unwrap();
2433 let queries = [
2434 "PREFIX ex: <http://ex/> SELECT ?j ?t ?v WHERE { ex:work ex:partOf+ ?j . \
2435 ?j ex:a ex:Journal ; ex:title ?t ; ex:id ?i . ?i ex:value ?v }",
2436 "PREFIX ex: <http://ex/> SELECT ?j ?t WHERE { ex:work ex:partOf+ ?j \
2437 OPTIONAL { ?j ex:title ?t } }",
2438 ];
2439 for q in queries {
2440 let run = |floor: f64| {
2441 crate::bgp::with_probe_floor(floor, || {
2442 let (_, sols) = eval_sparql(&rete, q).unwrap();
2443 let mut v: Vec<String> = sols.iter().map(|b| format!("{b:?}")).collect();
2444 v.sort();
2445 v
2446 })
2447 };
2448 let hashed = run(f64::MAX);
2449 assert!(!hashed.is_empty());
2450 assert_eq!(run(0.0), hashed, "{q}");
2451 }
2452 }
2453 fn people() -> Rete {
2456 const TYPE: &str = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>";
2457 let e = |n: String| format!("<http://ex/{n}>");
2458 let mut t: Vec<(String, String, String)> = Vec::new();
2459 for i in 0..200 {
2460 t.push((
2461 e(format!("p{i}")),
2462 e("knows".into()),
2463 e(format!("p{}", (i + 1) % 200)),
2464 ));
2465 t.push((e(format!("p{i}")), e("name".into()), format!("\"N{i}\"")));
2466 if i % 2 == 0 {
2467 t.push((e(format!("p{i}")), e("age".into()), format!("\"{i}\"")));
2468 }
2469 let class = if i % 10 == 0 { "Manager" } else { "Person" };
2470 t.push((e(format!("p{i}")), TYPE.into(), e(class.into())));
2471 }
2472 for i in [3, 4] {
2473 t.push((e(format!("p{i}")), TYPE.into(), e("Agent".into())));
2474 }
2475 let refs: Vec<(&str, &str, &str)> = t
2476 .iter()
2477 .map(|(s, p, o)| (s.as_str(), p.as_str(), o.as_str()))
2478 .collect();
2479 Rete::open(&rete_from(&refs)).unwrap()
2480 }
2481
2482 fn rows_at(rete: &Rete, q: &str, floor: f64) -> Vec<String> {
2486 crate::bgp::with_probe_floor(floor, || {
2487 let (_, sols) = eval_sparql(rete, q).unwrap();
2488 let mut v: Vec<String> = sols.iter().map(|b| format!("{b:?}")).collect();
2489 v.sort();
2490 v
2491 })
2492 }
2493
2494 #[test]
2497 fn correlated_exists_and_minus_match_whole_evaluation() {
2498 let rete = people();
2499 let pre = "PREFIX ex: <http://ex/> ";
2500 let bodies = [
2501 "SELECT ?s WHERE { ?s ex:knows ?o . FILTER NOT EXISTS { ?s ex:age ?a } }",
2503 "SELECT ?s WHERE { ?s ex:knows ?o . FILTER EXISTS { ?s ex:age ?a } }",
2504 "SELECT ?s WHERE { ?s ex:name ?n . FILTER NOT EXISTS { ?s ex:knows ?o . ?o ex:age ?a } }",
2506 "SELECT ?o WHERE { ?s ex:knows ?o . FILTER NOT EXISTS { ?x ex:knows ?o . ?x ex:age ?a } }",
2508 "SELECT ?s WHERE { ?s ex:name ?n . FILTER NOT EXISTS { ?s ex:nothere ?a } }",
2510 "SELECT ?s WHERE { ?s ex:name ?n . FILTER EXISTS { ?s ex:nothere ?a } }",
2511 "SELECT ?s WHERE { ?s ex:name ?n . FILTER NOT EXISTS { ?z ex:age \"4\" } }",
2513 "SELECT ?s WHERE { ?s ex:name ?n . FILTER EXISTS { ex:p4 ex:age \"4\" } }",
2515 "SELECT ?s WHERE { ?s ex:name ?n . BIND(CONCAT(?n, \"!\") AS ?m) FILTER NOT EXISTS { ?m ex:age ?a } }",
2517 "SELECT ?s WHERE { ?s ex:knows ?o . MINUS { ?s ex:age ?a } }",
2519 "SELECT ?o WHERE { ?s ex:knows ?o . MINUS { ?x ex:knows ?o . ?x ex:age ?a } }",
2520 "SELECT ?s WHERE { ?s ex:name ?n . MINUS { ?z ex:age ?a } }",
2521 "SELECT ?s WHERE { ?s ex:name ?n . MINUS { ?s ex:nothere ?a } }",
2522 "SELECT ?s ?a WHERE { ?s ex:name ?n . OPTIONAL { ?s ex:age ?a } MINUS { ?a ex:nothing ?z } }",
2524 "SELECT ?s WHERE { VALUES ?s { ex:p1 ex:p2 ex:p3 } FILTER NOT EXISTS { ?s ex:age ?a } }",
2526 ];
2527 for body in bodies {
2528 let q = format!("{pre}{body}");
2529 let whole = rows_at(&rete, &q, f64::MAX);
2530 assert_eq!(rows_at(&rete, &q, 0.0), whole, "{body}");
2531 }
2532 let n = |body: &str| rows_at(&rete, &format!("{pre}{body}"), 0.0).len();
2534 assert_eq!(n(bodies[0]), 100);
2535 assert_eq!(n(bodies[9]), 100);
2536 assert_eq!(n(bodies[11]), 200);
2537 }
2538
2539 #[test]
2543 fn a_union_receives_the_bindings_of_the_side_joined_with_it() {
2544 let rete = people();
2545 let pre = "PREFIX ex: <http://ex/> ";
2546 let bodies = [
2547 "SELECT ?r ?p ?v WHERE { VALUES ?r { ex:p1 ex:p2 ex:p3 ex:nobody } \
2548 { ?r ?p ?v } UNION { ?r ex:knows ?n . ?n ?p ?v } }",
2549 "SELECT ?r ?p ?v WHERE { { ?r ?p ?v } UNION { ?r ex:knows ?n . ?n ?p ?v } \
2550 VALUES ?r { ex:p1 ex:p2 ex:p3 } }",
2551 "SELECT ?r ?v WHERE { VALUES ?r { ex:p4 ex:p8 } { ?r ex:age ?v } UNION \
2553 { ?r ex:nothere ?v } UNION { ?r ex:knows ?v } }",
2554 "SELECT ?x ?v WHERE { ?x ex:age \"4\" . { ?x ex:knows ?v } UNION { ?x ex:name ?v } }",
2556 "SELECT ?r ?v ?w WHERE { VALUES ?r { ex:p3 ex:p6 } { ?r ex:knows ?v } UNION \
2558 { ?r ex:age ?w } }",
2559 "SELECT ?r ?v WHERE { VALUES ?r { ex:p3 UNDEF } { ?r ex:age ?v } UNION { ?r ex:name ?v } }",
2561 ];
2562 for body in bodies {
2563 let q = format!("{pre}{body}");
2564 let whole = rows_at(&rete, &q, f64::MAX);
2565 assert!(!whole.is_empty(), "{body}");
2566 assert_eq!(rows_at(&rete, &q, 0.0), whole, "{body}");
2567 }
2568 let q = format!("{pre}{}", bodies[0]);
2572 assert_eq!(rows_at(&rete, &q, 0.0).len(), 11 + 13);
2573 }
2574
2575 #[test]
2578 fn an_unbound_class_joined_on_a_bound_subject_probes_per_subject() {
2579 let rete = people();
2580 let pre = "PREFIX ex: <http://ex/> ";
2581 let bodies = [
2582 "SELECT ?s ?c WHERE { ?s ex:age ?a . ?s a ?c }",
2583 "SELECT ?s ?o ?c WHERE { ?s ex:knows ?o . ?o a ?c }",
2584 "SELECT ?c (COUNT(*) AS ?n) WHERE { ?s ex:age ?a . ?s a ?c } GROUP BY ?c",
2585 "SELECT ?s ?c WHERE { ?s ex:knows ?o . ?o a ?c . ?s a ?c }",
2587 "SELECT ?s ?c WHERE { ?s ?p ?x . ?s a ?c . FILTER(?p = ex:age) }",
2589 ];
2590 for body in bodies {
2591 let q = format!("{pre}{body}");
2592 let whole = rows_at(&rete, &q, f64::MAX);
2593 assert!(!whole.is_empty(), "{body}");
2594 assert_eq!(rows_at(&rete, &q, 0.0), whole, "{body}");
2595 }
2596 let q = format!("{pre}{}", bodies[0]);
2598 assert_eq!(rows_at(&rete, &q, 0.0).len(), 101);
2599 }
2600
2601 #[test]
2603 fn a_named_graph_is_sized_by_its_own_triples() {
2604 use crate::write_dataset_v05 as write_dataset;
2605 let mut db = DictionaryBuilder::new();
2606 let big: Vec<(String, String, String)> = (0..300)
2607 .map(|i| {
2608 (
2609 format!("<http://ex/s{i}>"),
2610 "<http://ex/p>".to_string(),
2611 format!("<http://ex/o{i}>"),
2612 )
2613 })
2614 .collect();
2615 let tiny = (
2616 "<http://ex/s0>".to_string(),
2617 "<http://ex/q>".to_string(),
2618 "<http://ex/o0>".to_string(),
2619 );
2620 for (s, p, o) in big.iter().chain(std::iter::once(&tiny)) {
2621 db.observe(s, p, o);
2622 }
2623 let dict = db.build();
2624 let mut default = GraphIndexBuilder::new();
2625 for (s, p, o) in &big {
2626 default.push(dict.encode(s, p, o).unwrap());
2627 }
2628 let mut small = GraphIndexBuilder::new();
2629 small.push(dict.encode(&tiny.0, &tiny.1, &tiny.2).unwrap());
2630 let named = vec![("<http://ex/g>".to_string(), small.build())];
2631 let bytes = write_dataset(&dict, &default.build(), &named, true, &[], 0);
2632 let rete = Rete::open(&bytes).unwrap();
2633 let g = rete.graph_index("<http://ex/g>").unwrap();
2634 assert_eq!(rete.index_triples(g), 1, "the graph's own count");
2635 assert_eq!(
2636 rete.index_triples(rete.default_index()),
2637 rete.header().quad_count,
2638 "the default graph keeps the file's total"
2639 );
2640 let (_, sols) = eval_sparql(
2641 &rete,
2642 "SELECT ?o WHERE { GRAPH <http://ex/g> { <http://ex/s0> <http://ex/q> ?o } }",
2643 )
2644 .unwrap();
2645 assert_eq!(sols.len(), 1);
2646 }
2647
2648 #[test]
2651 fn a_quad_index_graph_is_sized_by_its_directory_count() {
2652 use crate::ingest::{assemble_dataset_with_layout, GraphLayout};
2653 let q = |s: &str, g: Option<&str>| -> crate::ingest::RawQuad {
2654 (
2655 format!("<http://ex/{s}>"),
2656 "<http://ex/p>".to_string(),
2657 "<http://ex/o>".to_string(),
2658 g.map(|g| format!("<http://ex/{g}>")),
2659 )
2660 };
2661 let mut quads: Vec<_> = (0..50).map(|i| q(&format!("d{i}"), None)).collect();
2662 quads.extend((0..7).map(|i| q(&format!("a{i}"), Some("ga"))));
2663 quads.extend((0..3).map(|i| q(&format!("b{i}"), Some("gb"))));
2664 let (bytes, _) = assemble_dataset_with_layout(
2665 quads,
2666 false,
2667 false,
2668 None,
2669 crate::pyramid::PyramidAlgo::Louvain,
2670 crate::index::PermSet::ALL,
2671 GraphLayout::Quad,
2672 |_, _| Vec::<u8>::new(),
2673 );
2674 let rete = Rete::open(&bytes).unwrap();
2675 assert_eq!(
2676 rete.index_triples(rete.graph_index("<http://ex/ga>").unwrap()),
2677 7
2678 );
2679 assert_eq!(
2680 rete.index_triples(rete.graph_index("<http://ex/gb>").unwrap()),
2681 3
2682 );
2683 let (_, sols) = eval_sparql(
2684 &rete,
2685 "SELECT ?s WHERE { GRAPH <http://ex/ga> { ?s <http://ex/p> <http://ex/o> . \
2686 FILTER NOT EXISTS { ?s <http://ex/p> <http://ex/zz> } } }",
2687 )
2688 .unwrap();
2689 assert_eq!(sols.len(), 7);
2690 }
2691}