use crate::algebra::{Binding, Solution};
use crate::federation::FederatedSubquery;
use anyhow::Result;
#[derive(Debug, Clone)]
pub enum FederationPlan {
Service(Box<FederatedSubquery>),
Join(Box<FederationPlan>, Box<FederationPlan>),
Union(Box<FederationPlan>, Box<FederationPlan>),
LeftJoin(Box<FederationPlan>, Box<FederationPlan>),
Minus(Box<FederationPlan>, Box<FederationPlan>),
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum CombineOp {
Join,
Union,
LeftJoin,
Minus,
}
impl CombineOp {
pub(crate) fn build(self, left: FederationPlan, right: FederationPlan) -> FederationPlan {
let (l, r) = (Box::new(left), Box::new(right));
match self {
CombineOp::Join => FederationPlan::Join(l, r),
CombineOp::Union => FederationPlan::Union(l, r),
CombineOp::LeftJoin => FederationPlan::LeftJoin(l, r),
CombineOp::Minus => FederationPlan::Minus(l, r),
}
}
}
pub(crate) fn merge_compatible_bindings(left: &Binding, right: &Binding) -> Option<Binding> {
let mut merged = left.clone();
for (var, value) in right {
match merged.get(var) {
Some(existing) if existing != value => return None,
Some(_) => {}
None => {
merged.insert(var.clone(), value.clone());
}
}
}
Some(merged)
}
pub(crate) fn join_solutions(left: &Solution, right: &Solution) -> Solution {
let mut out = Solution::new();
for l in left {
for r in right {
if let Some(merged) = merge_compatible_bindings(l, r) {
out.push(merged);
}
}
}
out
}
pub(crate) fn left_join_solutions(left: &Solution, right: &Solution) -> Solution {
let mut out = Solution::new();
for l in left {
let mut matched = false;
for r in right {
if let Some(merged) = merge_compatible_bindings(l, r) {
out.push(merged);
matched = true;
}
}
if !matched {
out.push(l.clone());
}
}
out
}
pub(crate) fn minus_solutions(left: &Solution, right: &Solution) -> Solution {
left.iter()
.filter(|l| !right.iter().any(|r| minus_removes(l, r)))
.cloned()
.collect()
}
pub(crate) fn minus_removes(left: &Binding, right: &Binding) -> bool {
let mut shares_variable = false;
for (var, left_value) in left {
if let Some(right_value) = right.get(var) {
if left_value != right_value {
return false;
}
shares_variable = true;
}
}
shares_variable
}
pub(crate) fn escape_sparql_string(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for ch in value.chars() {
match ch {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
_ => out.push(ch),
}
}
out
}
pub(crate) fn literal_to_sparql(lit: &crate::algebra::Literal) -> String {
let escaped = escape_sparql_string(&lit.value);
if let Some(lang) = &lit.language {
format!("\"{escaped}\"@{lang}")
} else if let Some(dt) = &lit.datatype {
let dt_str = dt.as_str();
if dt_str == "http://www.w3.org/2001/XMLSchema#string" {
format!("\"{escaped}\"")
} else {
format!("\"{escaped}\"^^<{dt_str}>")
}
} else {
format!("\"{escaped}\"")
}
}
pub(crate) fn binary_operator_symbol(op: &crate::algebra::BinaryOperator) -> Option<&'static str> {
use crate::algebra::BinaryOperator as B;
Some(match op {
B::Add => "+",
B::Subtract => "-",
B::Multiply => "*",
B::Divide => "/",
B::Equal => "=",
B::NotEqual => "!=",
B::Less => "<",
B::LessEqual => "<=",
B::Greater => ">",
B::GreaterEqual => ">=",
B::And => "&&",
B::Or => "||",
B::SameTerm | B::In | B::NotIn => return None,
})
}
pub(crate) fn property_path_to_sparql(path: &crate::algebra::PropertyPath) -> Result<String> {
use crate::algebra::PropertyPath as P;
Ok(match path {
P::Iri(iri) => format!("<{}>", iri.as_str()),
P::Variable(v) => format!("?{}", v.name()),
P::Inverse(inner) => format!("^{}", property_path_to_sparql(inner)?),
P::Sequence(a, b) => format!(
"({}/{})",
property_path_to_sparql(a)?,
property_path_to_sparql(b)?
),
P::Alternative(a, b) => format!(
"({}|{})",
property_path_to_sparql(a)?,
property_path_to_sparql(b)?
),
P::ZeroOrMore(inner) => format!("({})*", property_path_to_sparql(inner)?),
P::OneOrMore(inner) => format!("({})+", property_path_to_sparql(inner)?),
P::ZeroOrOne(inner) => format!("({})?", property_path_to_sparql(inner)?),
P::NegatedPropertySet(paths) => {
let inner: Result<Vec<String>> = paths.iter().map(property_path_to_sparql).collect();
format!("!({})", inner?.join("|"))
}
})
}