use super::{
ast::{
EdgeDirection, EdgePattern, GqlLiteral, GqlPredicate, GqlQuery, NodePattern, PathSegment,
},
parser::parse_gql,
GqlTranslateError,
};
#[derive(Debug, Clone)]
pub struct GqlToSparqlTranslator {
pub base_prefix: String,
}
impl Default for GqlToSparqlTranslator {
fn default() -> Self {
Self::new()
}
}
impl GqlToSparqlTranslator {
pub fn new() -> Self {
Self {
base_prefix: "http://example.org/".to_string(),
}
}
pub fn with_prefix(prefix: &str) -> Self {
Self {
base_prefix: prefix.to_string(),
}
}
pub fn translate(&self, gql: &str) -> Result<String, GqlTranslateError> {
let query = parse_gql(gql)?;
if query.return_vars.is_empty() {
return Err(GqlTranslateError::EmptyReturn);
}
Ok(self.translate_ast(&query))
}
fn translate_ast(&self, query: &GqlQuery) -> String {
let select_vars: String = query
.return_vars
.iter()
.map(|v| format!("?{v}"))
.collect::<Vec<_>>()
.join(" ");
let mut triples: Vec<String> = Vec::new();
let mut filters: Vec<String> = Vec::new();
let segments = &query.match_pattern;
let mut idx = 0;
let first_var = match segments.first() {
Some(PathSegment::Node(n)) => {
let v = self.node_var_name(n, idx);
for t in self.node_to_sparql(n, &v) {
triples.push(t);
}
idx += 1;
v
}
_ => {
String::new()
}
};
let mut prev_var = first_var;
while idx + 1 < segments.len() {
if let (PathSegment::Edge(edge), PathSegment::Node(node)) =
(&segments[idx], &segments[idx + 1])
{
let next_var = self.node_var_name(node, idx + 1);
let edge_triple = self.edge_to_sparql(&prev_var, edge, &next_var);
triples.push(edge_triple);
for t in self.node_to_sparql(node, &next_var) {
triples.push(t);
}
prev_var = next_var;
idx += 2;
} else {
break;
}
}
if let Some(pred) = &query.where_pred {
let (aux_triple, filter) = self.predicate_to_sparql(pred);
triples.push(aux_triple);
filters.push(filter);
}
let mut body_lines: Vec<String> = triples.iter().map(|t| format!(" {t}")).collect();
for f in &filters {
body_lines.push(format!(" {f}"));
}
let body = body_lines.join("\n");
format!("SELECT {select_vars} WHERE {{\n{body}\n}}")
}
fn node_var_name(&self, node: &NodePattern, segment_idx: usize) -> String {
node.var
.clone()
.unwrap_or_else(|| format!("_node{segment_idx}"))
}
pub fn node_to_sparql(&self, node: &NodePattern, var: &str) -> Vec<String> {
let mut triples = Vec::new();
if let Some(label) = &node.label {
let iri = self.iri(label);
triples.push(format!("?{var} a {iri} ."));
}
for (prop, lit) in &node.props {
let pred_iri = self.iri(prop);
let lit_str = self.literal_to_sparql(lit);
triples.push(format!("?{var} {pred_iri} {lit_str} ."));
}
triples
}
pub fn edge_to_sparql(&self, prev_var: &str, edge: &EdgePattern, next_var: &str) -> String {
let pred = if let Some(label) = &edge.label {
self.iri(label)
} else {
let ev = edge
.var
.as_deref()
.map(|v| format!("?{v}"))
.unwrap_or_else(|| "?_p".to_string());
ev
};
match edge.direction {
EdgeDirection::Forward => {
format!("?{prev_var} {pred} ?{next_var} .")
}
EdgeDirection::Backward => {
format!("?{next_var} {pred} ?{prev_var} .")
}
}
}
fn predicate_to_sparql(&self, pred: &GqlPredicate) -> (String, String) {
let prop_iri = self.iri(&pred.prop);
let aux_var = format!("{}_{}", pred.var, pred.prop);
let lit_str = self.literal_to_sparql(&pred.value);
let triple = format!("?{} {} ?{} .", pred.var, prop_iri, aux_var);
let filter = format!("FILTER(?{aux_var} = {lit_str})");
(triple, filter)
}
pub fn literal_to_sparql(&self, lit: &GqlLiteral) -> String {
match lit {
GqlLiteral::Str(s) => {
let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
format!("\"{escaped}\"")
}
GqlLiteral::Int(n) => n.to_string(),
GqlLiteral::Float(f) => {
format!("{f:?}")
}
GqlLiteral::Bool(b) => {
format!("\"{}\"^^xsd:boolean", if *b { "true" } else { "false" })
}
}
}
fn iri(&self, local: &str) -> String {
format!("<{}{}>", self.base_prefix, local)
}
}