1use super::*;
7
8use crate::bgp::{PatternTerm, TriplePattern};
9use spargebra::algebra::{
10 AggregateExpression, AggregateFunction, Expression, Function, GraphPattern, OrderExpression,
11 PropertyPathExpression, QueryDataset,
12};
13use spargebra::term::{NamedNodePattern, TermPattern, TriplePattern as SpTriplePattern};
14use spargebra::Query;
15
16pub(super) fn lower_pattern(pattern: &GraphPattern) -> Result<Select, SparqlError> {
18 let mut sel = Select::default();
19 let plan = build(pattern, &mut sel, false)?;
20 sel.plan = plan;
21 Ok(sel)
22}
23
24pub(super) fn lower_select(
26 pattern: &GraphPattern,
27 dataset: &Option<QueryDataset>,
28) -> Result<Select, SparqlError> {
29 let mut sel = lower_pattern(pattern)?;
30 if let Some(ds) = dataset {
31 sel.from = ds.default.iter().map(|n| n.to_string()).collect();
32 sel.from_named = ds
33 .named
34 .as_ref()
35 .map(|gs| gs.iter().map(|n| n.to_string()).collect());
36 }
37 Ok(sel)
38}
39
40pub(super) fn strip_version(query: &str) -> &str {
46 let mut rest = query;
47 loop {
48 let t = rest.trim_start();
49 if let Some(after_hash) = t.strip_prefix('#') {
50 match after_hash.split_once('\n') {
51 Some((_, r)) => rest = r, None => return query, }
54 continue;
55 }
56 if t.len() >= 7 && t[..7].eq_ignore_ascii_case("VERSION") {
57 let after = t[7..].trim_start();
58 if let Some(q) = after.chars().next().filter(|c| *c == '"' || *c == '\'') {
59 if let Some(close) = after[q.len_utf8()..].find(q) {
60 return &after[q.len_utf8() + close + q.len_utf8()..];
61 }
62 }
63 }
64 return query; }
66}
67
68pub(super) fn parse_query(query: &str) -> Result<Query, SparqlError> {
73 let sparql12 = super::sparql12::is_sparql12(super::sparql12::version(query));
74 let text = super::sparql12::rewrite(strip_version(query), sparql12)?;
75 Query::parse(&text, None).map_err(|e| SparqlError::Parse(e.to_string()))
76}
77
78pub fn parse_select(query: &str) -> Result<Select, SparqlError> {
80 let parsed = parse_query(query)?;
81 match parsed {
82 Query::Select {
83 pattern, dataset, ..
84 } => lower_select(&pattern, &dataset),
85 _ => Err(SparqlError::Unsupported("only SELECT is supported")),
86 }
87}
88
89pub fn query_predicates(query: &str) -> Result<std::collections::BTreeSet<String>, SparqlError> {
99 let parsed = parse_query(query)?;
100 let mut preds = std::collections::BTreeSet::new();
101 let pattern = match &parsed {
102 Query::Select { pattern, .. } => pattern,
103 Query::Ask { pattern, .. } => pattern,
104 Query::Construct { pattern, .. } => pattern,
105 Query::Describe { pattern, .. } => pattern,
106 };
107 collect_pattern_predicates(pattern, &mut preds);
108 Ok(preds)
109}
110
111fn collect_pattern_predicates(p: &GraphPattern, out: &mut std::collections::BTreeSet<String>) {
113 match p {
114 GraphPattern::Bgp { patterns } => {
115 for tp in patterns {
116 if let NamedNodePattern::NamedNode(n) = &tp.predicate {
117 out.insert(n.to_string());
118 }
119 }
120 }
121 GraphPattern::Path {
122 path: PropertyPathExpression::NamedNode(n),
123 ..
124 } => {
125 out.insert(n.to_string());
126 }
127 GraphPattern::Path { path, .. } => collect_path_predicates(path, out),
128 GraphPattern::Join { left, right }
129 | GraphPattern::Union { left, right }
130 | GraphPattern::Minus { left, right } => {
131 collect_pattern_predicates(left, out);
132 collect_pattern_predicates(right, out);
133 }
134 GraphPattern::LeftJoin { left, right, .. } => {
135 collect_pattern_predicates(left, out);
136 collect_pattern_predicates(right, out);
137 }
138 GraphPattern::Filter { inner, .. }
139 | GraphPattern::Extend { inner, .. }
140 | GraphPattern::OrderBy { inner, .. }
141 | GraphPattern::Project { inner, .. }
142 | GraphPattern::Distinct { inner }
143 | GraphPattern::Reduced { inner }
144 | GraphPattern::Slice { inner, .. }
145 | GraphPattern::Group { inner, .. }
146 | GraphPattern::Service { inner, .. }
147 | GraphPattern::Graph { inner, .. } => collect_pattern_predicates(inner, out),
148 _ => {}
149 }
150}
151
152fn collect_path_predicates(
154 path: &PropertyPathExpression,
155 out: &mut std::collections::BTreeSet<String>,
156) {
157 match path {
158 PropertyPathExpression::NamedNode(n) => {
159 out.insert(n.to_string());
160 }
161 PropertyPathExpression::Reverse(inner)
162 | PropertyPathExpression::ZeroOrMore(inner)
163 | PropertyPathExpression::OneOrMore(inner)
164 | PropertyPathExpression::ZeroOrOne(inner) => collect_path_predicates(inner, out),
165 PropertyPathExpression::Sequence(a, b) | PropertyPathExpression::Alternative(a, b) => {
166 collect_path_predicates(a, out);
167 collect_path_predicates(b, out);
168 }
169 PropertyPathExpression::NegatedPropertySet(_) => {}
170 }
171}
172
173fn build_left_spine(p: &GraphPattern, sel: &mut Select) -> Result<Plan, SparqlError> {
180 enum SpineOp {
181 Join(Plan),
182 LeftJoin(Plan, Option<FExpr>),
183 }
184 let mut ops: Vec<SpineOp> = Vec::new();
185 let mut cur = p;
186 loop {
187 match cur {
188 GraphPattern::Join { left, right } => {
189 ops.push(SpineOp::Join(build(right, sel, true)?));
190 cur = left;
191 }
192 GraphPattern::LeftJoin {
193 left,
194 right,
195 expression,
196 } => {
197 let cond = expression.as_ref().map(convert_expr).transpose()?;
198 ops.push(SpineOp::LeftJoin(build(right, sel, true)?, cond));
199 cur = left;
200 }
201 _ => break,
202 }
203 }
204 let mut plan = build(cur, sel, true)?;
206 for op in ops.into_iter().rev() {
208 plan = match op {
209 SpineOp::Join(r) => Plan::Join(Box::new(plan), Box::new(r)),
210 SpineOp::LeftJoin(r, c) => Plan::LeftJoin(Box::new(plan), Box::new(r), c),
211 };
212 }
213 Ok(plan)
214}
215
216fn build(mut p: &GraphPattern, sel: &mut Select, mut in_where: bool) -> Result<Plan, SparqlError> {
225 loop {
233 match p {
234 GraphPattern::Distinct { inner } | GraphPattern::Reduced { inner } => {
235 if in_where || !sel.project.is_empty() {
239 return Ok(Plan::Subquery(Box::new(lower_pattern(p)?)));
240 }
241 sel.distinct = true;
242 p = inner;
243 }
244 GraphPattern::Slice {
245 inner,
246 start,
247 length,
248 } => {
249 if in_where || !sel.project.is_empty() {
254 return Ok(Plan::Subquery(Box::new(lower_pattern(p)?)));
255 }
256 sel.offset = *start;
257 sel.limit = *length;
258 p = inner;
259 }
260 GraphPattern::Project { inner, variables } if !in_where && sel.project.is_empty() => {
263 for v in variables {
264 sel.project.push(v.as_str().to_string());
265 }
266 p = inner;
267 }
268 GraphPattern::Group {
269 inner,
270 variables,
271 aggregates,
272 } => {
273 let by = variables.iter().map(|v| v.as_str().to_string()).collect();
274 let mut aggs = Vec::with_capacity(aggregates.len());
275 let mut pre: Vec<(String, FExpr)> = Vec::new();
276 for (var, ae) in aggregates {
277 aggs.push((var.as_str().to_string(), convert_agg(ae, &mut pre)?));
278 }
279 sel.group = Some(GroupSpec { by, aggs, pre });
280 in_where = true; p = inner;
282 }
283 GraphPattern::OrderBy { inner, expression } => {
284 for oe in expression {
285 let (e, desc) = match oe {
286 OrderExpression::Asc(e) => (e, false),
287 OrderExpression::Desc(e) => (e, true),
288 };
289 sel.order.push((convert_expr(e)?, desc));
290 }
291 p = inner;
292 }
293 GraphPattern::Extend {
296 inner,
297 variable,
298 expression,
299 } if !in_where => {
300 sel.extends
301 .push((variable.as_str().to_string(), convert_expr(expression)?));
302 p = inner;
303 }
304 _ => break,
305 }
306 }
307 match p {
308 GraphPattern::Bgp { patterns } => Ok(lower_bgp(patterns, &mut sel.star_counter)),
309 GraphPattern::Join { .. } | GraphPattern::LeftJoin { .. } => build_left_spine(p, sel),
317 GraphPattern::Union { left, right } => Ok(Plan::Union(
318 Box::new(build(left, sel, true)?),
319 Box::new(build(right, sel, true)?),
320 )),
321 GraphPattern::Minus { left, right } => Ok(Plan::Minus(
322 Box::new(build(left, sel, true)?),
323 Box::new(build(right, sel, true)?),
324 )),
325 GraphPattern::Graph { name, inner } => {
326 let target = match name {
327 NamedNodePattern::NamedNode(n) => GraphTarget::Named(n.to_string()),
328 NamedNodePattern::Variable(v) => GraphTarget::Var(v.as_str().to_string()),
329 };
330 Ok(Plan::Graph(target, Box::new(build(inner, sel, true)?)))
331 }
332 GraphPattern::Path {
333 subject,
334 path,
335 object,
336 } => Ok(Plan::Path(
337 term_to_pattern(subject),
338 lower_path(path)?,
339 term_to_pattern(object),
340 )),
341 GraphPattern::Values {
342 variables,
343 bindings,
344 } => {
345 let vars = variables.iter().map(|v| v.as_str().to_string()).collect();
346 let rows = bindings
347 .iter()
348 .map(|row| {
349 row.iter()
350 .map(|g| g.as_ref().map(|t| t.to_string()))
351 .collect()
352 })
353 .collect();
354 Ok(Plan::Values(vars, rows))
355 }
356 GraphPattern::Filter { expr, inner } => {
357 let had_group = sel.group.is_some();
360 let inner_plan = build(inner, sel, true)?;
361 let fexpr = convert_expr(expr)?;
362 if sel.group.is_some() && !had_group {
363 sel.having.push(fexpr);
364 Ok(inner_plan)
365 } else {
366 Ok(Plan::Filter(fexpr, Box::new(inner_plan)))
367 }
368 }
369 GraphPattern::Project { inner, variables } => {
371 if in_where || !sel.project.is_empty() {
376 let sub = lower_pattern(p)?;
377 return Ok(Plan::Subquery(Box::new(sub)));
378 }
379 for v in variables {
380 sel.project.push(v.as_str().to_string());
381 }
382 build(inner, sel, in_where)
383 }
384 GraphPattern::Distinct { inner } | GraphPattern::Reduced { inner } => {
385 if in_where || !sel.project.is_empty() {
386 return Ok(Plan::Subquery(Box::new(lower_pattern(p)?)));
387 }
388 sel.distinct = true;
389 build(inner, sel, in_where)
390 }
391 GraphPattern::Slice {
392 inner,
393 start,
394 length,
395 } => {
396 if in_where || !sel.project.is_empty() {
397 return Ok(Plan::Subquery(Box::new(lower_pattern(p)?)));
398 }
399 sel.offset = *start;
400 sel.limit = *length;
401 build(inner, sel, in_where)
402 }
403 GraphPattern::Group {
404 inner,
405 variables,
406 aggregates,
407 } => {
408 let by = variables.iter().map(|v| v.as_str().to_string()).collect();
409 let mut aggs = Vec::with_capacity(aggregates.len());
410 let mut pre: Vec<(String, FExpr)> = Vec::new();
411 for (var, ae) in aggregates {
412 aggs.push((var.as_str().to_string(), convert_agg(ae, &mut pre)?));
413 }
414 sel.group = Some(GroupSpec { by, aggs, pre });
415 build(inner, sel, true)
418 }
419 GraphPattern::Extend {
420 inner,
421 variable,
422 expression,
423 } => {
424 let var = variable.as_str().to_string();
425 let fexpr = convert_expr(expression)?;
426 if in_where {
427 Ok(Plan::Extend(var, fexpr, Box::new(build(inner, sel, true)?)))
430 } else {
431 sel.extends.push((var, fexpr));
434 build(inner, sel, in_where)
435 }
436 }
437 GraphPattern::OrderBy { inner, expression } => {
438 for oe in expression {
439 let (e, desc) = match oe {
440 OrderExpression::Asc(e) => (e, false),
441 OrderExpression::Desc(e) => (e, true),
442 };
443 sel.order.push((convert_expr(e)?, desc));
444 }
445 build(inner, sel, in_where)
446 }
447 GraphPattern::Service {
453 name,
454 inner,
455 silent,
456 } => {
457 let endpoint = match name {
458 NamedNodePattern::NamedNode(n) => n.as_str().to_string(),
459 NamedNodePattern::Variable(_) => {
460 return Err(SparqlError::Unsupported("SERVICE with a variable endpoint"))
461 }
462 };
463 let mut vars = std::collections::BTreeSet::new();
464 collect_pattern_variables(inner, &mut vars);
465 let query = Query::Select {
466 dataset: None,
467 pattern: (**inner).clone(),
468 base_iri: None,
469 }
470 .to_string();
471 Ok(Plan::Service {
472 silent: *silent,
473 endpoint,
474 vars: vars.into_iter().collect(),
475 query,
476 })
477 }
478 }
479}
480
481fn collect_pattern_variables(p: &GraphPattern, out: &mut std::collections::BTreeSet<String>) {
486 let term_var = |t: &TermPattern, out: &mut std::collections::BTreeSet<String>| {
487 if let TermPattern::Variable(v) = t {
488 out.insert(v.as_str().to_string());
489 }
490 };
491 match p {
492 GraphPattern::Bgp { patterns } => {
493 for tp in patterns {
494 term_var(&tp.subject, out);
495 if let NamedNodePattern::Variable(v) = &tp.predicate {
496 out.insert(v.as_str().to_string());
497 }
498 term_var(&tp.object, out);
499 }
500 }
501 GraphPattern::Path {
502 subject, object, ..
503 } => {
504 term_var(subject, out);
505 term_var(object, out);
506 }
507 GraphPattern::Values { variables, .. } => {
508 for v in variables {
509 out.insert(v.as_str().to_string());
510 }
511 }
512 GraphPattern::Join { left, right }
513 | GraphPattern::Union { left, right }
514 | GraphPattern::Minus { left, right } => {
515 collect_pattern_variables(left, out);
516 collect_pattern_variables(right, out);
517 }
518 GraphPattern::LeftJoin { left, right, .. } => {
519 collect_pattern_variables(left, out);
520 collect_pattern_variables(right, out);
521 }
522 GraphPattern::Extend {
523 inner, variable, ..
524 } => {
525 out.insert(variable.as_str().to_string());
526 collect_pattern_variables(inner, out);
527 }
528 GraphPattern::Group {
529 inner,
530 variables,
531 aggregates,
532 } => {
533 for v in variables {
534 out.insert(v.as_str().to_string());
535 }
536 for (v, _) in aggregates {
537 out.insert(v.as_str().to_string());
538 }
539 collect_pattern_variables(inner, out);
540 }
541 GraphPattern::Graph { name, inner } => {
542 if let NamedNodePattern::Variable(v) = name {
543 out.insert(v.as_str().to_string());
544 }
545 collect_pattern_variables(inner, out);
546 }
547 GraphPattern::Project { inner, variables } => {
548 for v in variables {
549 out.insert(v.as_str().to_string());
550 }
551 collect_pattern_variables(inner, out);
552 }
553 GraphPattern::Filter { inner, .. }
554 | GraphPattern::OrderBy { inner, .. }
555 | GraphPattern::Distinct { inner }
556 | GraphPattern::Reduced { inner }
557 | GraphPattern::Slice { inner, .. }
558 | GraphPattern::Service { inner, .. } => collect_pattern_variables(inner, out),
559 }
560}
561
562fn convert_agg(
563 ae: &AggregateExpression,
564 pre: &mut Vec<(String, FExpr)>,
565) -> Result<Agg, SparqlError> {
566 match ae {
567 AggregateExpression::CountSolutions { distinct } => Ok(Agg::CountStar {
568 distinct: *distinct,
569 }),
570 AggregateExpression::FunctionCall {
571 name,
572 expr,
573 distinct,
574 } => {
575 let var = match expr {
576 Expression::Variable(v) => v.as_str().to_string(),
577 other => {
582 let name = format!("__agg{}", pre.len());
583 pre.push((name.clone(), convert_expr(other)?));
584 name
585 }
586 };
587 Ok(match name {
588 AggregateFunction::Count => Agg::Count(var, *distinct),
589 AggregateFunction::Sum => Agg::Sum(var),
590 AggregateFunction::Avg => Agg::Avg(var),
591 AggregateFunction::Min => Agg::Min(var),
592 AggregateFunction::Max => Agg::Max(var),
593 AggregateFunction::Sample => Agg::Sample(var),
594 AggregateFunction::GroupConcat { separator } => Agg::GroupConcat(
595 var,
596 separator.clone().unwrap_or_else(|| " ".to_string()),
597 *distinct,
598 ),
599 _ => return Err(SparqlError::Unsupported("aggregate function")),
600 })
601 }
602 }
603}
604
605fn lower_path(p: &PropertyPathExpression) -> Result<PathAst, SparqlError> {
607 Ok(match p {
608 PropertyPathExpression::NamedNode(n) => PathAst::Pred(n.to_string(), false),
609 PropertyPathExpression::Reverse(inner) => reverse(lower_path(inner)?),
610 PropertyPathExpression::OneOrMore(inner) => {
611 PathAst::Rep(Box::new(lower_path(inner)?), Rep::OneOrMore)
612 }
613 PropertyPathExpression::ZeroOrMore(inner) => {
614 PathAst::Rep(Box::new(lower_path(inner)?), Rep::ZeroOrMore)
615 }
616 PropertyPathExpression::ZeroOrOne(inner) => {
617 PathAst::Rep(Box::new(lower_path(inner)?), Rep::ZeroOrOne)
618 }
619 PropertyPathExpression::Sequence(a, b) => {
620 PathAst::Seq(Box::new(lower_path(a)?), Box::new(lower_path(b)?))
621 }
622 PropertyPathExpression::Alternative(a, b) => {
623 PathAst::Alt(Box::new(lower_path(a)?), Box::new(lower_path(b)?))
624 }
625 PropertyPathExpression::NegatedPropertySet(preds) => {
626 PathAst::NegatedSet(preds.iter().map(|n| n.to_string()).collect(), false)
627 }
628 })
629}
630
631fn convert_expr(e: &Expression) -> Result<FExpr, SparqlError> {
633 let bin = |op, l: &Expression, r: &Expression| -> Result<FExpr, SparqlError> {
634 Ok(FExpr::Compare(
635 op,
636 Box::new(convert_expr(l)?),
637 Box::new(convert_expr(r)?),
638 ))
639 };
640 let arith = |op, l: &Expression, r: &Expression| -> Result<FExpr, SparqlError> {
641 Ok(FExpr::Arith(
642 op,
643 Box::new(convert_expr(l)?),
644 Box::new(convert_expr(r)?),
645 ))
646 };
647 Ok(match e {
648 Expression::Variable(v) => FExpr::Var(v.as_str().to_string()),
649 Expression::NamedNode(n) => FExpr::Const(n.to_string()),
650 Expression::Literal(l) => FExpr::Const(l.to_string()),
651 Expression::Equal(l, r) => bin(Op::Eq, l, r)?,
652 Expression::Greater(l, r) => bin(Op::Gt, l, r)?,
653 Expression::GreaterOrEqual(l, r) => bin(Op::Ge, l, r)?,
654 Expression::Less(l, r) => bin(Op::Lt, l, r)?,
655 Expression::LessOrEqual(l, r) => bin(Op::Le, l, r)?,
656 Expression::And(l, r) => FExpr::And(Box::new(convert_expr(l)?), Box::new(convert_expr(r)?)),
657 Expression::Or(l, r) => FExpr::Or(Box::new(convert_expr(l)?), Box::new(convert_expr(r)?)),
658 Expression::Not(inner) => FExpr::Not(Box::new(convert_expr(inner)?)),
659 Expression::Bound(v) => FExpr::Bound(v.as_str().to_string()),
660 Expression::Add(l, r) => arith(ArithOp::Add, l, r)?,
661 Expression::Subtract(l, r) => arith(ArithOp::Sub, l, r)?,
662 Expression::Multiply(l, r) => arith(ArithOp::Mul, l, r)?,
663 Expression::Divide(l, r) => arith(ArithOp::Div, l, r)?,
664 Expression::Coalesce(items) => FExpr::Coalesce(
665 items
666 .iter()
667 .map(convert_expr)
668 .collect::<Result<Vec<_>, _>>()?,
669 ),
670 Expression::UnaryPlus(e) => convert_expr(e)?,
671 Expression::UnaryMinus(e) => FExpr::Arith(
672 ArithOp::Sub,
673 Box::new(FExpr::Const("0".into())),
674 Box::new(convert_expr(e)?),
675 ),
676 Expression::If(c, t, e) => FExpr::If(
677 Box::new(convert_expr(c)?),
678 Box::new(convert_expr(t)?),
679 Box::new(convert_expr(e)?),
680 ),
681 Expression::In(e, list) => FExpr::In(
682 Box::new(convert_expr(e)?),
683 list.iter().map(convert_expr).collect::<Result<_, _>>()?,
684 ),
685 Expression::SameTerm(l, r) => {
686 FExpr::SameTerm(Box::new(convert_expr(l)?), Box::new(convert_expr(r)?))
687 }
688 Expression::Exists(pattern) => {
689 let mut sub = Select::default();
693 let plan = build(pattern, &mut sub, true)?;
694 FExpr::Exists(Box::new(plan))
695 }
696 Expression::FunctionCall(func, params) => {
697 let builtin = match func {
698 Function::Str => Builtin::Str,
699 Function::Concat => Builtin::Concat,
700 Function::SubStr => Builtin::SubStr,
701 Function::StrBefore => Builtin::StrBefore,
702 Function::StrAfter => Builtin::StrAfter,
703 Function::StrLen => Builtin::StrLen,
704 Function::UCase => Builtin::UCase,
705 Function::LCase => Builtin::LCase,
706 Function::Abs => Builtin::Abs,
707 Function::Ceil => Builtin::Ceil,
708 Function::Floor => Builtin::Floor,
709 Function::Round => Builtin::Round,
710 Function::Contains => Builtin::Contains,
711 Function::StrStarts => Builtin::StrStarts,
712 Function::StrEnds => Builtin::StrEnds,
713 Function::IsIri => Builtin::IsIri,
714 Function::IsBlank => Builtin::IsBlank,
715 Function::IsLiteral => Builtin::IsLiteral,
716 Function::IsNumeric => Builtin::IsNumeric,
717 Function::Datatype => Builtin::Datatype,
718 Function::Lang => Builtin::Lang,
719 Function::Regex => Builtin::Regex,
720 Function::LangMatches => Builtin::LangMatches,
721 Function::StrDt => Builtin::StrDt,
722 Function::StrLang => Builtin::StrLang,
723 Function::Iri => Builtin::Iri,
724 Function::EncodeForUri => Builtin::EncodeForUri,
725 Function::Replace => Builtin::Replace,
726 Function::Md5 => Builtin::Md5,
727 Function::Sha1 => Builtin::Sha1,
728 Function::Sha256 => Builtin::Sha256,
729 Function::Sha384 => Builtin::Sha384,
730 Function::Sha512 => Builtin::Sha512,
731 Function::Year => Builtin::Year,
732 Function::Month => Builtin::Month,
733 Function::Day => Builtin::Day,
734 Function::Hours => Builtin::Hours,
735 Function::Minutes => Builtin::Minutes,
736 Function::Seconds => Builtin::Seconds,
737 Function::Timezone => Builtin::Timezone,
738 Function::Tz => Builtin::Tz,
739 Function::Rand => Builtin::Rand,
740 Function::Uuid => Builtin::Uuid,
741 Function::StrUuid => Builtin::StrUuid,
742 Function::BNode => Builtin::BNode,
743 Function::Triple => Builtin::TripleTerm,
745 Function::IsTriple => Builtin::IsTriple,
746 Function::Subject => Builtin::Subject,
747 Function::Predicate => Builtin::Predicate,
748 Function::Object => Builtin::Object,
749 Function::Custom(nn) => match nn.as_str() {
752 "http://www.w3.org/2001/XMLSchema#integer" => Builtin::CastInteger,
753 "http://www.w3.org/2001/XMLSchema#decimal" => Builtin::CastDecimal,
754 "http://www.w3.org/2001/XMLSchema#float" => Builtin::CastFloat,
755 "http://www.w3.org/2001/XMLSchema#double" => Builtin::CastDouble,
756 "http://www.w3.org/2001/XMLSchema#boolean" => Builtin::CastBoolean,
757 "http://www.w3.org/2001/XMLSchema#string" => Builtin::CastString,
758 "http://www.opengis.net/def/function/geosparql/sfContains" => {
760 Builtin::GeoSfContains
761 }
762 "http://www.opengis.net/def/function/geosparql/sfWithin" => {
763 Builtin::GeoSfWithin
764 }
765 "http://www.opengis.net/def/function/geosparql/sfIntersects" => {
766 Builtin::GeoSfIntersects
767 }
768 "http://www.opengis.net/def/function/geosparql/sfDisjoint" => {
769 Builtin::GeoSfDisjoint
770 }
771 "http://www.opengis.net/def/function/geosparql/sfEquals" => {
772 Builtin::GeoSfEquals
773 }
774 "http://www.opengis.net/def/function/geosparql/distance" => {
775 Builtin::GeoDistance
776 }
777 "http://www.opengis.net/def/function/geosparql/envelope" => {
778 Builtin::GeoEnvelope
779 }
780 "https://w3id.org/rete/geo3/function/distance3D" => Builtin::Geo3Distance,
782 "https://w3id.org/rete/geo3/function/contains3D" => Builtin::Geo3Contains,
783 "https://w3id.org/rete/geo3/function/within3D" => Builtin::Geo3Within,
784 "https://w3id.org/rete/geo3/function/adjacent3D" => Builtin::Geo3Adjacent,
785 _ => return Err(SparqlError::UnsupportedFunction(nn.as_str().to_string())),
786 },
787 _ => return Err(SparqlError::Unsupported("built-in function")),
788 };
789 let args = params
790 .iter()
791 .map(convert_expr)
792 .collect::<Result<Vec<_>, _>>()?;
793 FExpr::Func(builtin, args)
794 }
795 })
796}
797
798fn convert(tp: &SpTriplePattern) -> TriplePattern {
799 TriplePattern {
800 s: term_to_pattern(&tp.subject),
801 p: named_to_pattern(&tp.predicate),
802 o: term_to_pattern(&tp.object),
803 }
804}
805
806fn is_var_quoted(t: &TermPattern) -> bool {
809 matches!(t, TermPattern::Triple(inner) if ground_quoted_token(inner).is_none())
810}
811
812fn lower_bgp(patterns: &[SpTriplePattern], counter: &mut usize) -> Plan {
821 if !patterns
822 .iter()
823 .any(|tp| is_var_quoted(&tp.subject) || is_var_quoted(&tp.object))
824 {
825 return Plan::Bgp(patterns.iter().map(convert).collect());
826 }
827
828 let mut regular_vars: std::collections::BTreeSet<String> = Default::default();
831 for tp in patterns {
832 for t in [&tp.subject, &tp.object] {
833 if let TermPattern::Variable(v) = t {
834 regular_vars.insert(v.as_str().to_string());
835 }
836 }
837 if let NamedNodePattern::Variable(v) = &tp.predicate {
838 regular_vars.insert(v.as_str().to_string());
839 }
840 }
841
842 let mut st = StarRewrite {
843 counter,
844 regular_vars,
845 filters: Vec::new(),
846 binds: Vec::new(),
847 bound: Default::default(),
848 seen: Default::default(),
849 };
850 let rewritten: Vec<TriplePattern> = patterns
851 .iter()
852 .map(|tp| TriplePattern {
853 s: st.rewrite_term(&tp.subject),
854 p: named_to_pattern(&tp.predicate),
855 o: st.rewrite_term(&tp.object),
856 })
857 .collect();
858
859 let mut plan = Plan::Bgp(rewritten);
863 for (var, expr) in st.binds {
864 plan = Plan::Extend(var, expr, Box::new(plan));
865 }
866 if let Some(cond) = st
867 .filters
868 .into_iter()
869 .reduce(|a, b| FExpr::And(Box::new(a), Box::new(b)))
870 {
871 plan = Plan::Filter(cond, Box::new(plan));
872 }
873 plan
874}
875
876struct StarRewrite<'a> {
878 counter: &'a mut usize,
879 regular_vars: std::collections::BTreeSet<String>,
880 filters: Vec<FExpr>,
881 binds: Vec<(String, FExpr)>,
882 bound: std::collections::BTreeSet<String>,
883 seen: std::collections::BTreeMap<String, String>,
889}
890
891fn star_accessor(f: Builtin, qt: &str) -> FExpr {
892 FExpr::Func(f, vec![FExpr::Var(qt.to_string())])
893}
894fn star_same(a: FExpr, b: FExpr) -> FExpr {
895 FExpr::SameTerm(Box::new(a), Box::new(b))
896}
897
898impl StarRewrite<'_> {
899 fn rewrite_term(&mut self, t: &TermPattern) -> PatternTerm {
902 if is_var_quoted(t) {
903 let TermPattern::Triple(inner) = t else {
904 unreachable!("is_var_quoted implies Triple");
905 };
906 let key = format!("{inner:?}");
910 if let Some(qt) = self.seen.get(&key) {
911 return PatternTerm::Var(qt.clone());
912 }
913 *self.counter += 1;
914 let qt = format!("__qt{}", self.counter);
915 self.seen.insert(key, qt.clone());
916 self.filters
917 .push(FExpr::Func(Builtin::IsTriple, vec![FExpr::Var(qt.clone())]));
918 self.decompose(inner, &qt);
919 PatternTerm::Var(qt)
920 } else {
921 term_to_pattern(t)
922 }
923 }
924
925 fn decompose(&mut self, inner: &SpTriplePattern, qt: &str) {
928 self.constrain(&inner.subject, star_accessor(Builtin::Subject, qt));
929 match &inner.predicate {
930 NamedNodePattern::NamedNode(n) => self.filters.push(star_same(
931 star_accessor(Builtin::Predicate, qt),
932 FExpr::Const(n.to_string()),
933 )),
934 NamedNodePattern::Variable(v) => {
935 self.constrain_var(v.as_str(), star_accessor(Builtin::Predicate, qt))
936 }
937 }
938 self.constrain(&inner.object, star_accessor(Builtin::Object, qt));
939 }
940
941 fn constrain(&mut self, t: &TermPattern, acc: FExpr) {
942 match t {
943 TermPattern::NamedNode(n) => self
944 .filters
945 .push(star_same(acc, FExpr::Const(n.to_string()))),
946 TermPattern::Literal(l) => self
947 .filters
948 .push(star_same(acc, FExpr::Const(l.to_string()))),
949 TermPattern::Variable(v) => self.constrain_var(v.as_str(), acc),
950 TermPattern::BlankNode(b) => self.constrain_var(&b.to_string(), acc),
951 TermPattern::Triple(nested) => {
952 if let Some(tok) = ground_quoted_token(nested) {
953 self.filters.push(star_same(acc, FExpr::Const(tok)));
954 } else {
955 *self.counter += 1;
958 let qt2 = format!("__qt{}", self.counter);
959 self.binds.push((qt2.clone(), acc));
960 self.filters.push(FExpr::Func(
961 Builtin::IsTriple,
962 vec![FExpr::Var(qt2.clone())],
963 ));
964 self.decompose(nested, &qt2);
965 }
966 }
967 }
968 }
969
970 fn constrain_var(&mut self, name: &str, acc: FExpr) {
973 let name = name.to_string();
974 if self.regular_vars.contains(&name) || self.bound.contains(&name) {
975 self.filters.push(star_same(acc, FExpr::Var(name)));
976 } else {
977 self.binds.push((name.clone(), acc));
978 self.bound.insert(name);
979 }
980 }
981}
982
983fn term_to_pattern(t: &TermPattern) -> PatternTerm {
984 match t {
985 TermPattern::NamedNode(n) => PatternTerm::Const(n.to_string()),
986 TermPattern::Literal(l) => PatternTerm::Const(l.to_string()),
987 TermPattern::BlankNode(b) => PatternTerm::Var(b.to_string()),
991 TermPattern::Variable(v) => PatternTerm::Var(v.as_str().to_string()),
992 TermPattern::Triple(tp) => match ground_quoted_token(tp) {
1000 Some(tok) => PatternTerm::Const(tok),
1001 None => PatternTerm::Const("<< rdf-star inner-var pattern >>".to_string()),
1002 },
1003 }
1004}
1005
1006fn ground_quoted_token(tp: &SpTriplePattern) -> Option<String> {
1010 fn ground(t: &TermPattern) -> Option<String> {
1011 match t {
1012 TermPattern::NamedNode(n) => Some(n.to_string()),
1013 TermPattern::Literal(l) => Some(l.to_string()),
1014 TermPattern::Triple(inner) => ground_quoted_token(inner),
1015 TermPattern::BlankNode(_) | TermPattern::Variable(_) => None,
1016 }
1017 }
1018 let s = ground(&tp.subject)?;
1019 let p = match &tp.predicate {
1020 NamedNodePattern::NamedNode(n) => n.to_string(),
1021 NamedNodePattern::Variable(_) => return None,
1022 };
1023 let o = ground(&tp.object)?;
1024 Some(format!("<<{s} {p} {o}>>"))
1025}
1026
1027fn named_to_pattern(n: &NamedNodePattern) -> PatternTerm {
1028 match n {
1029 NamedNodePattern::NamedNode(nn) => PatternTerm::Const(nn.to_string()),
1030 NamedNodePattern::Variable(v) => PatternTerm::Var(v.as_str().to_string()),
1031 }
1032}
1033
1034#[cfg(test)]
1035mod tests {
1036 use super::*;
1037
1038 #[test]
1039 fn version_parsing_select_only_and_uniform_errors() {
1040 let q = " # leading\n VERSION \"1.2\" SELECT * WHERE {}";
1041 assert!(strip_version(q).trim_start().starts_with("SELECT"));
1042 assert!(parse_select(q).is_ok());
1043 assert_eq!(strip_version("# comment only"), "# comment only");
1044 assert_eq!(
1045 strip_version("VERSION invalid SELECT * WHERE {}"),
1046 "VERSION invalid SELECT * WHERE {}"
1047 );
1048 assert!(matches!(
1049 parse_select("ASK {}"),
1050 Err(SparqlError::Unsupported(_))
1051 ));
1052 assert!(matches!(
1053 parse_select("not sparql"),
1054 Err(SparqlError::Parse(_))
1055 ));
1056 }
1057
1058 #[test]
1059 fn lowers_every_graph_operator_dataset_modifier_and_subquery() {
1060 let query = r#"
1061 SELECT DISTINCT ?s ?x
1062 FROM <http://ex/default>
1063 FROM NAMED <http://ex/named>
1064 WHERE {
1065 { ?s <http://ex/p> ?o .
1066 OPTIONAL { ?s <http://ex/q> ?q FILTER(?q > 1) }
1067 }
1068 UNION { GRAPH ?g { ?s <http://ex/r> ?o } }
1069 MINUS { ?s <http://ex/bad> ?o }
1070 VALUES (?v ?u) { (1 UNDEF) (2 "two") }
1071 BIND(?v + 1 AS ?x)
1072 FILTER(BOUND(?x) && (?x IN (1, 2)))
1073 }
1074 ORDER BY ASC(?s) DESC(?x)
1075 OFFSET 1 LIMIT 2
1076 "#;
1077 let select = parse_select(query).unwrap();
1078 assert!(select.distinct);
1079 assert_eq!(select.project, ["s", "x"]);
1080 assert_eq!(select.offset, 1);
1081 assert_eq!(select.limit, Some(2));
1082 assert_eq!(select.order.len(), 2);
1083 assert_eq!(select.from, ["<http://ex/default>"]);
1084 assert_eq!(select.from_named.unwrap(), ["<http://ex/named>"]);
1085
1086 let subquery = parse_select(
1087 "SELECT ?s WHERE { ?s <http://ex/p> ?o . { SELECT REDUCED ?s WHERE { ?s <http://ex/q> ?v } LIMIT 1 } }",
1088 )
1089 .unwrap();
1090 assert!(matches!(subquery.plan, Plan::Join(..)));
1091
1092 let fixed_service = parse_select(
1093 "SELECT * WHERE { SERVICE SILENT <http://example.test/sparql> { ?s <http://ex/p> ?o OPTIONAL { ?s <http://ex/q> ?q } } }",
1094 )
1095 .unwrap();
1096 assert!(matches!(
1097 fixed_service.plan,
1098 Plan::Service { silent: true, .. }
1099 ));
1100 assert!(matches!(
1101 parse_select("SELECT * WHERE { SERVICE ?endpoint { ?s ?p ?o } }"),
1102 Err(SparqlError::Unsupported(_))
1103 ));
1104 }
1105
1106 #[test]
1107 fn lowers_aggregates_having_preexpressions_and_all_property_path_shapes() {
1108 let grouped = parse_select(
1109 r#"
1110 SELECT ?g
1111 (COUNT(*) AS ?all)
1112 (COUNT(DISTINCT ?v) AS ?count)
1113 (SUM(?v * 2) AS ?sum)
1114 (AVG(?v) AS ?avg)
1115 (MIN(?v) AS ?min)
1116 (MAX(?v) AS ?max)
1117 (SAMPLE(?v) AS ?sample)
1118 (GROUP_CONCAT(DISTINCT ?label; SEPARATOR="|") AS ?labels)
1119 WHERE { ?s <http://ex/g> ?g ; <http://ex/v> ?v ; <http://ex/label> ?label }
1120 GROUP BY ?g
1121 HAVING (SUM(?v) > 0)
1122 ORDER BY DESC(?sum)
1123 "#,
1124 )
1125 .unwrap();
1126 let group = grouped.group.unwrap();
1127 assert_eq!(group.by, ["g"]);
1128 assert_eq!(group.aggs.len(), 9);
1129 assert_eq!(group.pre.len(), 1);
1130 assert_eq!(grouped.having.len(), 1);
1131
1132 for path in [
1133 "<http://ex/p>",
1134 "^<http://ex/p>",
1135 "<http://ex/p>+",
1136 "<http://ex/p>*",
1137 "<http://ex/p>?",
1138 "<http://ex/p>/<http://ex/q>",
1139 "<http://ex/p>|<http://ex/q>",
1140 "!(<http://ex/p>|<http://ex/q>)",
1141 ] {
1142 let q = format!("SELECT * WHERE {{ ?s {path} ?o }}");
1143 assert!(parse_select(&q).is_ok(), "{path}");
1144 }
1145 }
1146
1147 #[test]
1148 fn lowers_expression_and_builtin_matrix_in_projection_aliases() {
1149 for expression in [
1150 "?v = 1",
1151 "?v > 1",
1152 "?v >= 1",
1153 "?v < 1",
1154 "?v <= 1",
1155 "(?v = 1) || (?v = 2)",
1156 "!(?v = 1)",
1157 "+?v",
1158 "-?v",
1159 "?v - 1",
1160 "?v * 2",
1161 "?v / 2",
1162 "COALESCE(?missing, ?v)",
1163 "IF(BOUND(?v), ?v, 0)",
1164 "sameTerm(?v, 1)",
1165 "EXISTS { ?s <http://ex/inside> ?v BIND(STR(?v) AS ?text) }",
1166 "STR(?v)",
1167 "CONCAT(\"a\", \"b\")",
1168 "SUBSTR(\"abc\", 2)",
1169 "STRBEFORE(\"abc\", \"b\")",
1170 "STRAFTER(\"abc\", \"b\")",
1171 "STRLEN(\"abc\")",
1172 "UCASE(\"abc\")",
1173 "LCASE(\"ABC\")",
1174 "ABS(-2)",
1175 "CEIL(1.2)",
1176 "FLOOR(1.2)",
1177 "ROUND(1.5)",
1178 "CONTAINS(\"abc\", \"b\")",
1179 "STRSTARTS(\"abc\", \"a\")",
1180 "STRENDS(\"abc\", \"c\")",
1181 "isIRI(<http://ex/a>)",
1182 "isBLANK(?v)",
1183 "isLITERAL(\"x\")",
1184 "isNUMERIC(1)",
1185 "DATATYPE(\"x\")",
1186 "LANG(\"x\"@en)",
1187 "REGEX(\"abc\", \"a\")",
1188 "LANGMATCHES(\"en-GB\", \"en\")",
1189 "STRDT(\"x\", <http://ex/type>)",
1190 "STRLANG(\"x\", \"en\")",
1191 "IRI(\"http://ex/a\")",
1192 "ENCODE_FOR_URI(\"a b\")",
1193 "REPLACE(\"abc\", \"b\", \"x\")",
1194 "MD5(\"abc\")",
1195 "SHA1(\"abc\")",
1196 "SHA256(\"abc\")",
1197 "SHA384(\"abc\")",
1198 "SHA512(\"abc\")",
1199 "YEAR(\"2024-01-01T00:00:00Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>)",
1200 "MONTH(\"2024-01-01T00:00:00Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>)",
1201 "DAY(\"2024-01-01T00:00:00Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>)",
1202 "HOURS(\"2024-01-01T00:00:00Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>)",
1203 "MINUTES(\"2024-01-01T00:00:00Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>)",
1204 "SECONDS(\"2024-01-01T00:00:00Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>)",
1205 "TIMEZONE(\"2024-01-01T00:00:00Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>)",
1206 "TZ(\"2024-01-01T00:00:00Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>)",
1207 "RAND()",
1208 "UUID()",
1209 "STRUUID()",
1210 "BNODE(\"x\")",
1211 "<http://www.w3.org/2001/XMLSchema#integer>(\"1\")",
1212 "<http://www.w3.org/2001/XMLSchema#decimal>(\"1.2\")",
1213 "<http://www.w3.org/2001/XMLSchema#float>(\"1\")",
1214 "<http://www.w3.org/2001/XMLSchema#double>(\"1\")",
1215 "<http://www.w3.org/2001/XMLSchema#boolean>(\"true\")",
1216 "<http://www.w3.org/2001/XMLSchema#string>(1)",
1217 "<http://www.opengis.net/def/function/geosparql/sfContains>(?a, ?b)",
1218 "<http://www.opengis.net/def/function/geosparql/sfWithin>(?a, ?b)",
1219 "<http://www.opengis.net/def/function/geosparql/sfIntersects>(?a, ?b)",
1220 "<http://www.opengis.net/def/function/geosparql/sfDisjoint>(?a, ?b)",
1221 "<http://www.opengis.net/def/function/geosparql/sfEquals>(?a, ?b)",
1222 "<http://www.opengis.net/def/function/geosparql/distance>(?a, ?b, <http://www.opengis.net/def/uom/OGC/1.0/metre>)",
1223 "<http://www.opengis.net/def/function/geosparql/envelope>(?a)",
1224 "<https://w3id.org/rete/geo3/function/distance3D>(?a, ?b)",
1225 "<https://w3id.org/rete/geo3/function/contains3D>(?a, ?b)",
1226 "<https://w3id.org/rete/geo3/function/within3D>(?a, ?b)",
1227 "<https://w3id.org/rete/geo3/function/adjacent3D>(?a, ?b)",
1228 "<https://w3id.org/rete/geo3/function/adjacent3D>(?a, ?b, 5)",
1229 ] {
1230 let q = format!("SELECT ({expression} AS ?result) WHERE {{ VALUES ?v {{ 1 }} }}");
1231 assert!(parse_select(&q).is_ok(), "{expression}");
1232 }
1233 assert!(matches!(
1234 parse_select("SELECT (<http://ex/unsupported>(1) AS ?x) WHERE {}"),
1235 Err(SparqlError::UnsupportedFunction(iri)) if iri == "http://ex/unsupported"
1236 ));
1237 }
1238
1239 #[test]
1240 fn predicate_collection_walks_query_forms_wrappers_and_complex_paths() {
1241 let select = query_predicates(
1242 "SELECT * WHERE { { ?s (<http://ex/p>|^<http://ex/q>)/<http://ex/r>* ?o } UNION { GRAPH <http://ex/g> { ?s <http://ex/s> ?o } } OPTIONAL { ?s ?variable ?o } MINUS { ?s <http://ex/t> ?o } }",
1243 )
1244 .unwrap();
1245 for p in ["p", "q", "r", "s", "t"] {
1246 assert!(select.contains(&format!("<http://ex/{p}>")));
1247 }
1248 assert_eq!(
1249 query_predicates("ASK { ?s <http://ex/ask> ?o }")
1250 .unwrap()
1251 .len(),
1252 1
1253 );
1254 assert_eq!(
1255 query_predicates("CONSTRUCT { ?s <http://ex/out> ?o } WHERE { ?s <http://ex/in> ?o }")
1256 .unwrap(),
1257 ["<http://ex/in>".to_string()].into_iter().collect()
1258 );
1259 assert_eq!(
1260 query_predicates("DESCRIBE ?s WHERE { ?s <http://ex/describe> ?o }")
1261 .unwrap()
1262 .len(),
1263 1
1264 );
1265 assert!(
1266 query_predicates("SELECT * WHERE { ?s !(<http://ex/p>|<http://ex/q>) ?o }")
1267 .unwrap()
1268 .is_empty()
1269 );
1270 }
1271
1272 #[test]
1273 fn rdf_star_lowering_handles_ground_variables_reuse_and_nested_terms() {
1274 for query in [
1275 "SELECT * WHERE { << <http://ex/s> <http://ex/p> \"o\" >> <http://ex/a> ?v }",
1276 "SELECT * WHERE { << ?s <http://ex/p> ?o >> <http://ex/a> ?v . << ?s <http://ex/p> ?o >> <http://ex/b> ?w }",
1277 "SELECT * WHERE { ?s <http://ex/p> ?o . << ?s ?pred ?o >> <http://ex/a> ?v }",
1278 "SELECT * WHERE { << << ?s <http://ex/p> ?o >> <http://ex/q> ?inner >> <http://ex/a> ?v }",
1279 ] {
1280 let lowered = parse_select(query).unwrap();
1281 assert!(lowered.star_counter > 0 || matches!(lowered.plan, Plan::Bgp(_)), "{query}");
1282 }
1283 }
1284
1285 #[test]
1286 fn sub_select_modifiers_stay_inside_the_subquery() {
1287 let outer = parse_select(
1293 "SELECT ?s WHERE { { SELECT ?s WHERE { ?s <http://ex/p> ?o } LIMIT 10 } } LIMIT 3",
1294 )
1295 .unwrap();
1296 assert_eq!(outer.limit, Some(3), "outer LIMIT was overwritten");
1297 assert!(
1298 matches!(outer.plan, Plan::Subquery(_)),
1299 "inner SELECT is not a subquery"
1300 );
1301 if let Plan::Subquery(inner) = &outer.plan {
1302 assert_eq!(
1303 inner.limit,
1304 Some(10),
1305 "inner LIMIT did not travel with the subquery"
1306 );
1307 }
1308
1309 let sliced = parse_select(
1311 "SELECT ?s WHERE { { SELECT ?s WHERE { ?s <http://ex/p> ?o } LIMIT 10 } } OFFSET 5 LIMIT 2",
1312 )
1313 .unwrap();
1314 assert_eq!((sliced.offset, sliced.limit), (5, Some(2)));
1315
1316 let distinct = parse_select(
1318 "SELECT ?s WHERE { { SELECT DISTINCT ?s WHERE { ?s <http://ex/p> ?o } } } LIMIT 3",
1319 )
1320 .unwrap();
1321 assert!(
1322 !distinct.distinct,
1323 "inner DISTINCT leaked to the outer query"
1324 );
1325 assert_eq!(distinct.limit, Some(3));
1326
1327 let plain = parse_select("SELECT ?s WHERE { ?s <http://ex/p> ?o } LIMIT 3").unwrap();
1329 assert_eq!(plain.limit, Some(3));
1330 assert!(matches!(plain.plan, Plan::Bgp(_)));
1331 }
1332}