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