use crate::{
model::{GraphName, NamedNode, Quad},
query::algebra::{Expression, GraphPattern, GraphTarget, QuadPattern, Update, UpdateOperation},
query::{AlgebraTriplePattern, TermPattern},
vocab::xsd,
OxirsError, Result, Store,
};
use std::collections::HashMap;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
const BATCH_THRESHOLD: usize = 100;
const MAX_BATCH_SIZE: usize = 10_000;
pub struct UpdateExecutor<'a> {
store: &'a dyn Store,
batch_enabled: bool,
batch_size: usize,
}
impl<'a> UpdateExecutor<'a> {
pub fn new(store: &'a dyn Store) -> Self {
Self {
store,
batch_enabled: true,
batch_size: BATCH_THRESHOLD,
}
}
pub fn with_batch_size(store: &'a dyn Store, batch_size: usize) -> Self {
Self {
store,
batch_enabled: true,
batch_size: batch_size.min(MAX_BATCH_SIZE),
}
}
pub fn without_batching(store: &'a dyn Store) -> Self {
Self {
store,
batch_enabled: false,
batch_size: 1,
}
}
pub fn execute(&self, update: &Update) -> Result<()> {
for operation in &update.operations {
self.execute_operation(operation)?;
}
Ok(())
}
fn execute_operation(&self, operation: &UpdateOperation) -> Result<()> {
match operation {
UpdateOperation::InsertData { data } => self.execute_insert_data(data),
UpdateOperation::DeleteData { data } => self.execute_delete_data(data),
UpdateOperation::DeleteWhere { pattern } => self.execute_delete_where(pattern),
UpdateOperation::Modify {
delete,
insert,
where_clause,
using: _,
} => self.execute_modify(delete, insert, where_clause),
UpdateOperation::Load {
source,
destination,
silent,
} => self.execute_load(source, destination, *silent),
UpdateOperation::Clear { graph, silent } => self.execute_clear(graph, *silent),
UpdateOperation::Create { graph, silent } => self.execute_create(graph, *silent),
UpdateOperation::Drop { graph, silent } => self.execute_drop(graph, *silent),
UpdateOperation::Copy {
source,
destination,
silent,
} => self.execute_copy(source, destination, *silent),
UpdateOperation::Move {
source,
destination,
silent,
} => self.execute_move(source, destination, *silent),
UpdateOperation::Add {
source,
destination,
silent,
} => self.execute_add(source, destination, *silent),
}
}
fn execute_insert_data(&self, data: &[Quad]) -> Result<()> {
if !self.batch_enabled || data.len() < self.batch_size {
for quad in data {
self.store.insert_quad(quad.clone())?;
}
return Ok(());
}
self.batch_insert_quads(data)
}
fn execute_delete_data(&self, data: &[Quad]) -> Result<()> {
if !self.batch_enabled || data.len() < self.batch_size {
for quad in data {
self.store.remove_quad(quad)?;
}
return Ok(());
}
self.batch_delete_quads(data)
}
fn batch_insert_quads(&self, quads: &[Quad]) -> Result<()> {
for chunk in quads.chunks(self.batch_size) {
let batch: Vec<Quad> = chunk.to_vec();
#[cfg(feature = "parallel")]
{
if batch.len() > 1000 {
let results: Vec<Result<bool>> = batch
.par_iter()
.map(|quad| self.store.insert_quad(quad.clone()))
.collect();
for result in results {
result?;
}
continue;
}
}
for quad in batch {
self.store.insert_quad(quad)?;
}
}
Ok(())
}
fn batch_delete_quads(&self, quads: &[Quad]) -> Result<()> {
for chunk in quads.chunks(self.batch_size) {
let batch: Vec<Quad> = chunk.to_vec();
#[cfg(feature = "parallel")]
{
if batch.len() > 1000 {
let results: Vec<Result<bool>> = batch
.par_iter()
.map(|quad| self.store.remove_quad(quad))
.collect();
for result in results {
result?;
}
continue;
}
}
for quad in &batch {
self.store.remove_quad(quad)?;
}
}
Ok(())
}
fn execute_delete_where(&self, patterns: &[QuadPattern]) -> Result<()> {
let mut all_matching_quads = Vec::new();
for pattern in patterns {
let matching_quads = self.find_matching_quads(pattern)?;
all_matching_quads.extend(matching_quads);
}
if self.batch_enabled && all_matching_quads.len() >= self.batch_size {
self.batch_delete_quads(&all_matching_quads)
} else {
for quad in all_matching_quads {
self.store.remove_quad(&quad)?;
}
Ok(())
}
}
fn execute_modify(
&self,
delete_patterns: &Option<Vec<QuadPattern>>,
insert_patterns: &Option<Vec<QuadPattern>>,
where_clause: &GraphPattern,
) -> Result<()> {
let solutions = self.evaluate_graph_pattern(where_clause)?;
let mut quads_to_delete = Vec::new();
let mut quads_to_insert = Vec::new();
for solution in solutions {
if let Some(delete_patterns) = delete_patterns {
for pattern in delete_patterns {
if let Some(quad) = self.instantiate_quad_pattern(pattern, &solution)? {
quads_to_delete.push(quad);
}
}
}
if let Some(insert_patterns) = insert_patterns {
for pattern in insert_patterns {
if let Some(quad) = self.instantiate_quad_pattern(pattern, &solution)? {
quads_to_insert.push(quad);
}
}
}
}
if !quads_to_delete.is_empty() {
if self.batch_enabled && quads_to_delete.len() >= self.batch_size {
self.batch_delete_quads(&quads_to_delete)?;
} else {
for quad in quads_to_delete {
self.store.remove_quad(&quad)?;
}
}
}
if !quads_to_insert.is_empty() {
if self.batch_enabled && quads_to_insert.len() >= self.batch_size {
self.batch_insert_quads(&quads_to_insert)?;
} else {
for quad in quads_to_insert {
self.store.insert_quad(quad)?;
}
}
}
Ok(())
}
fn execute_load(
&self,
source: &NamedNode,
_destination: &Option<NamedNode>,
_silent: bool,
) -> Result<()> {
Err(OxirsError::Update(format!(
"LOAD operation not implemented for source: {source}"
)))
}
fn execute_clear(&self, graph: &GraphTarget, _silent: bool) -> Result<()> {
match graph {
GraphTarget::Default => {
let default_graph = GraphName::DefaultGraph;
let quads = self
.store
.find_quads(None, None, None, Some(&default_graph))?;
for quad in quads {
self.store.remove_quad(&quad)?;
}
}
GraphTarget::Named(graph_name) => {
let graph = GraphName::NamedNode(graph_name.clone());
let quads = self.store.find_quads(None, None, None, Some(&graph))?;
for quad in quads {
self.store.remove_quad(&quad)?;
}
}
GraphTarget::All => {
let quads = self.store.find_quads(None, None, None, None)?;
for quad in quads {
self.store.remove_quad(&quad)?;
}
}
}
Ok(())
}
fn execute_create(&self, _graph: &NamedNode, _silent: bool) -> Result<()> {
Ok(())
}
fn execute_drop(&self, graph: &GraphTarget, _silent: bool) -> Result<()> {
self.execute_clear(graph, _silent)
}
fn execute_copy(
&self,
source: &GraphTarget,
destination: &GraphTarget,
_silent: bool,
) -> Result<()> {
self.execute_clear(destination, true)?;
let source_quads = self.get_quads_from_target(source)?;
for quad in source_quads {
let dest_quad = self.move_quad_to_target(&quad, destination)?;
self.store.insert_quad(dest_quad)?;
}
Ok(())
}
fn execute_move(
&self,
source: &GraphTarget,
destination: &GraphTarget,
_silent: bool,
) -> Result<()> {
self.execute_copy(source, destination, true)?;
self.execute_drop(source, true)?;
Ok(())
}
fn execute_add(
&self,
source: &GraphTarget,
destination: &GraphTarget,
_silent: bool,
) -> Result<()> {
let source_quads = self.get_quads_from_target(source)?;
for quad in source_quads {
let dest_quad = self.move_quad_to_target(&quad, destination)?;
self.store.insert_quad(dest_quad)?;
}
Ok(())
}
fn find_matching_quads(&self, pattern: &QuadPattern) -> Result<Vec<Quad>> {
let subject = self.term_pattern_to_subject(&pattern.subject)?;
let predicate = self.term_pattern_to_predicate(&pattern.predicate)?;
let object = self.term_pattern_to_object(&pattern.object)?;
let graph = pattern
.graph
.as_ref()
.map(|g| self.term_pattern_to_graph_name(g))
.transpose()?;
self.store.find_quads(
subject.as_ref(),
predicate.as_ref(),
object.as_ref(),
graph.as_ref(),
)
}
fn term_pattern_to_subject(
&self,
pattern: &TermPattern,
) -> Result<Option<crate::model::Subject>> {
match pattern {
TermPattern::NamedNode(n) => Ok(Some(crate::model::Subject::NamedNode(n.clone()))),
TermPattern::BlankNode(b) => Ok(Some(crate::model::Subject::BlankNode(b.clone()))),
TermPattern::Variable(_) => Ok(None), TermPattern::Literal(_) => Err(OxirsError::Update(
"Subject cannot be a literal".to_string(),
)),
TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
"RDF-star quoted triples as subjects not yet fully implemented".to_string(),
)),
}
}
fn term_pattern_to_predicate(
&self,
pattern: &TermPattern,
) -> Result<Option<crate::model::Predicate>> {
match pattern {
TermPattern::NamedNode(n) => Ok(Some(crate::model::Predicate::NamedNode(n.clone()))),
TermPattern::Variable(_) => Ok(None), TermPattern::BlankNode(_) | TermPattern::Literal(_) => Err(OxirsError::Update(
"Predicate must be a named node".to_string(),
)),
TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
"Quoted triples cannot be predicates".to_string(),
)),
}
}
fn term_pattern_to_object(
&self,
pattern: &TermPattern,
) -> Result<Option<crate::model::Object>> {
match pattern {
TermPattern::NamedNode(n) => Ok(Some(crate::model::Object::NamedNode(n.clone()))),
TermPattern::BlankNode(b) => Ok(Some(crate::model::Object::BlankNode(b.clone()))),
TermPattern::Literal(l) => Ok(Some(crate::model::Object::Literal(l.clone()))),
TermPattern::Variable(_) => Ok(None), TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
"RDF-star quoted triples as objects not yet fully implemented".to_string(),
)),
}
}
fn term_pattern_to_graph_name(&self, pattern: &TermPattern) -> Result<GraphName> {
match pattern {
TermPattern::NamedNode(n) => Ok(GraphName::NamedNode(n.clone())),
TermPattern::Variable(_) => Ok(GraphName::DefaultGraph), TermPattern::BlankNode(_) | TermPattern::Literal(_) => Err(OxirsError::Update(
"Graph name must be a named node".to_string(),
)),
TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
"Graph names cannot be quoted triples".to_string(),
)),
}
}
fn evaluate_graph_pattern(
&self,
pattern: &GraphPattern,
) -> Result<Vec<HashMap<String, crate::model::Term>>> {
use crate::query::{QueryEngine, QueryResult};
let query_engine = QueryEngine::new();
let sparql_query = self.graph_pattern_to_sparql(pattern)?;
match query_engine.query(&sparql_query, self.store)? {
QueryResult::Select {
variables: _,
bindings,
} => {
let mut solutions = Vec::new();
for binding in bindings {
let mut solution = HashMap::new();
for (var_name, term) in binding {
solution.insert(var_name, term);
}
solutions.push(solution);
}
Ok(solutions)
}
_ => Err(OxirsError::Update(
"Expected SELECT query result for WHERE clause evaluation".to_string(),
)),
}
}
fn instantiate_quad_pattern(
&self,
pattern: &QuadPattern,
solution: &HashMap<String, crate::model::Term>,
) -> Result<Option<Quad>> {
use crate::model::*;
let subject = match &pattern.subject {
TermPattern::Variable(var) => {
if let Some(term) = solution.get(var.name()) {
match term {
Term::NamedNode(n) => Subject::NamedNode(n.clone()),
Term::BlankNode(b) => Subject::BlankNode(b.clone()),
_ => return Ok(None), }
} else {
return Ok(None); }
}
TermPattern::NamedNode(n) => Subject::NamedNode(n.clone()),
TermPattern::BlankNode(b) => Subject::BlankNode(b.clone()),
TermPattern::Literal(_) => return Ok(None), TermPattern::QuotedTriple(_) => return Ok(None), };
let predicate = match &pattern.predicate {
TermPattern::Variable(var) => {
if let Some(Term::NamedNode(n)) = solution.get(var.name()) {
Predicate::NamedNode(n.clone())
} else {
return Ok(None); }
}
TermPattern::NamedNode(n) => Predicate::NamedNode(n.clone()),
_ => return Ok(None), };
let object = match &pattern.object {
TermPattern::Variable(var) => {
if let Some(term) = solution.get(var.name()) {
match term {
Term::NamedNode(n) => Object::NamedNode(n.clone()),
Term::BlankNode(b) => Object::BlankNode(b.clone()),
Term::Literal(l) => Object::Literal(l.clone()),
_ => return Ok(None), }
} else {
return Ok(None); }
}
TermPattern::NamedNode(n) => Object::NamedNode(n.clone()),
TermPattern::BlankNode(b) => Object::BlankNode(b.clone()),
TermPattern::Literal(l) => Object::Literal(l.clone()),
TermPattern::QuotedTriple(_) => return Ok(None), };
let graph_name = match &pattern.graph {
Some(graph_pattern) => match graph_pattern {
TermPattern::Variable(var) => {
if let Some(Term::NamedNode(n)) = solution.get(var.name()) {
GraphName::NamedNode(n.clone())
} else {
return Ok(None); }
}
TermPattern::NamedNode(n) => GraphName::NamedNode(n.clone()),
_ => return Ok(None), },
None => GraphName::DefaultGraph,
};
Ok(Some(Quad::new(subject, predicate, object, graph_name)))
}
fn get_quads_from_target(&self, target: &GraphTarget) -> Result<Vec<Quad>> {
match target {
GraphTarget::Default => {
let graph = GraphName::DefaultGraph;
self.store.find_quads(None, None, None, Some(&graph))
}
GraphTarget::Named(graph_name) => {
let graph = GraphName::NamedNode(graph_name.clone());
self.store.find_quads(None, None, None, Some(&graph))
}
GraphTarget::All => self.store.find_quads(None, None, None, None),
}
}
fn move_quad_to_target(&self, quad: &Quad, target: &GraphTarget) -> Result<Quad> {
match target {
GraphTarget::Default => Ok(Quad::new(
quad.subject().clone(),
quad.predicate().clone(),
quad.object().clone(),
GraphName::DefaultGraph,
)),
GraphTarget::Named(graph_name) => Ok(Quad::new(
quad.subject().clone(),
quad.predicate().clone(),
quad.object().clone(),
GraphName::NamedNode(graph_name.clone()),
)),
GraphTarget::All => {
Ok(quad.clone())
}
}
}
fn graph_pattern_to_sparql(&self, pattern: &GraphPattern) -> Result<String> {
use crate::query::algebra::*;
match pattern {
GraphPattern::Bgp(triple_patterns) => {
let mut sparql = String::from("SELECT * WHERE { ");
for (i, triple_pattern) in triple_patterns.iter().enumerate() {
if i > 0 {
sparql.push_str(" . ");
}
sparql.push_str(&self.triple_pattern_to_sparql(triple_pattern)?);
}
sparql.push_str(" }");
Ok(sparql)
}
GraphPattern::Join(left, right) => {
let left_sparql = self.graph_pattern_to_sparql(left)?;
let right_sparql = self.graph_pattern_to_sparql(right)?;
let left_where = self.extract_where_clause(&left_sparql)?;
let right_where = self.extract_where_clause(&right_sparql)?;
Ok(format!("SELECT * WHERE {{ {left_where} . {right_where} }}"))
}
GraphPattern::Filter { expr, inner } => {
let inner_sparql = self.graph_pattern_to_sparql(inner)?;
let inner_where = self.extract_where_clause(&inner_sparql)?;
let filter_expr = self.expression_to_sparql(expr)?;
Ok(format!(
"SELECT * WHERE {{ {inner_where} FILTER ({filter_expr}) }}"
))
}
GraphPattern::Union(left, right) => {
let left_sparql = self.graph_pattern_to_sparql(left)?;
let right_sparql = self.graph_pattern_to_sparql(right)?;
let left_where = self.extract_where_clause(&left_sparql)?;
let right_where = self.extract_where_clause(&right_sparql)?;
Ok(format!(
"SELECT * WHERE {{ {{ {left_where} }} UNION {{ {right_where} }} }}"
))
}
_ => Err(OxirsError::Update(format!(
"Graph pattern type not yet supported in SPARQL conversion: {pattern:?}"
))),
}
}
fn triple_pattern_to_sparql(&self, pattern: &AlgebraTriplePattern) -> Result<String> {
let subject = self.term_pattern_to_sparql(&pattern.subject)?;
let predicate = self.term_pattern_to_sparql(&pattern.predicate)?;
let object = self.term_pattern_to_sparql(&pattern.object)?;
Ok(format!("{subject} {predicate} {object}"))
}
fn term_pattern_to_sparql(&self, pattern: &TermPattern) -> Result<String> {
match pattern {
TermPattern::Variable(var) => Ok(format!("?{}", var.name())),
TermPattern::NamedNode(node) => Ok(format!("<{}>", node.as_str())),
TermPattern::BlankNode(blank) => Ok(format!("_:{}", blank.as_str())),
TermPattern::Literal(literal) => {
if let Some(lang) = literal.language() {
Ok(format!("\"{}\"@{}", literal.value(), lang))
} else if literal.datatype() != xsd::STRING.as_ref() {
Ok(format!("\"{}\"^^<{}>", literal.value(), literal.datatype()))
} else {
Ok(format!("\"{}\"", literal.value()))
}
}
TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
"RDF-star quoted triples not yet fully supported in SPARQL conversion".to_string(),
)),
}
}
#[allow(clippy::only_used_in_recursion)]
fn expression_to_sparql(&self, expr: &Expression) -> Result<String> {
match expr {
Expression::Variable(var) => Ok(format!("?{}", var.name())),
Expression::Term(term) => match term {
crate::model::Term::NamedNode(n) => Ok(format!("<{}>", n.as_str())),
crate::model::Term::BlankNode(b) => Ok(format!("_:{}", b.as_str())),
crate::model::Term::Literal(l) => {
if let Some(lang) = l.language() {
Ok(format!("\"{}\"@{}", l.value(), lang))
} else if l.datatype() != xsd::STRING.as_ref() {
Ok(format!("\"{}\"^^<{}>", l.value(), l.datatype()))
} else {
Ok(format!("\"{}\"", l.value()))
}
}
_ => Err(OxirsError::Update(
"Unsupported term type in expression".to_string(),
)),
},
Expression::Equal(left, right) => {
let left_sparql = self.expression_to_sparql(left)?;
let right_sparql = self.expression_to_sparql(right)?;
Ok(format!("({left_sparql} = {right_sparql})"))
}
Expression::And(left, right) => {
let left_sparql = self.expression_to_sparql(left)?;
let right_sparql = self.expression_to_sparql(right)?;
Ok(format!("({left_sparql} && {right_sparql})"))
}
Expression::Or(left, right) => {
let left_sparql = self.expression_to_sparql(left)?;
let right_sparql = self.expression_to_sparql(right)?;
Ok(format!("({left_sparql} || {right_sparql})"))
}
Expression::Not(inner) => {
let inner_sparql = self.expression_to_sparql(inner)?;
Ok(format!("(!{inner_sparql})"))
}
_ => Err(OxirsError::Update(format!(
"Expression type not yet supported in SPARQL conversion: {expr:?}"
))),
}
}
fn extract_where_clause(&self, sparql: &str) -> Result<String> {
if let Some(start) = sparql.find("WHERE {") {
let where_start = start + 7; if let Some(end) = sparql.rfind('}') {
let where_content = &sparql[where_start..end].trim();
Ok(where_content.to_string())
} else {
Err(OxirsError::Update(
"Malformed SPARQL query: missing closing brace".to_string(),
))
}
} else {
Err(OxirsError::Update(
"Malformed SPARQL query: missing WHERE clause".to_string(),
))
}
}
}
#[derive(Default)]
pub struct UpdateParser;
impl UpdateParser {
pub fn new() -> Self {
Self
}
pub fn parse(&self, update_str: &str) -> Result<Update> {
let trimmed = update_str.trim();
let (prefixes, remaining) = self.extract_prefixes(trimmed)?;
if remaining.contains("INSERT DATA") {
self.parse_insert_data(&remaining, prefixes)
} else if remaining.contains("DELETE DATA") {
self.parse_delete_data(&remaining, prefixes)
} else if self.is_delete_where_shorthand(&remaining) {
self.parse_delete_where(&remaining, prefixes)
} else if remaining.contains("DELETE") && remaining.contains("WHERE") {
self.parse_delete_modify(&remaining, prefixes)
} else if remaining.contains("INSERT") && remaining.contains("WHERE") {
self.parse_insert_where(&remaining, prefixes)
} else if remaining.contains("CLEAR") {
self.parse_clear(&remaining, prefixes)
} else {
Err(OxirsError::Parse(format!(
"Unsupported UPDATE operation: {}",
remaining
)))
}
}
fn is_delete_where_shorthand(&self, update_str: &str) -> bool {
if let Some(delete_pos) = update_str.find("DELETE") {
if let Some(where_pos) = update_str.find("WHERE") {
let between = &update_str[delete_pos + 6..where_pos];
return !between.contains('{');
}
}
false
}
fn extract_prefixes(&self, update_str: &str) -> Result<(HashMap<String, NamedNode>, String)> {
let mut prefixes = HashMap::new();
let mut remaining = update_str.to_string();
loop {
let trimmed = remaining.trim();
if let Some(prefix_start) = trimmed.find("PREFIX") {
if prefix_start == 0 || trimmed[..prefix_start].chars().all(|c| c.is_whitespace()) {
let after_prefix = &trimmed[prefix_start + 6..];
if let Some(colon_pos) = after_prefix.find(':') {
if let Some(iri_start) = after_prefix.find('<') {
if let Some(iri_end) = after_prefix.find('>') {
let prefix = after_prefix[..colon_pos].trim().to_string();
let iri_str = &after_prefix[iri_start + 1..iri_end];
let iri_node = NamedNode::new(iri_str).map_err(|e| {
OxirsError::Parse(format!("Invalid prefix IRI: {e}"))
})?;
prefixes.insert(prefix, iri_node);
remaining = after_prefix[iri_end + 1..].to_string();
continue;
}
}
}
}
}
break;
}
Ok((prefixes, remaining.trim().to_string()))
}
fn parse_insert_data(
&self,
update_str: &str,
prefixes: HashMap<String, NamedNode>,
) -> Result<Update> {
use crate::query::algebra::UpdateOperation;
let data_start = update_str.find('{');
let data_end = update_str.rfind('}');
if let (Some(start), Some(end)) = (data_start, data_end) {
let data_block = update_str[start + 1..end].trim();
let quads = self.parse_quad_data(data_block, &prefixes)?;
Ok(Update {
base: None,
prefixes,
operations: vec![UpdateOperation::InsertData { data: quads }],
})
} else {
Err(OxirsError::Parse(
"Malformed INSERT DATA: missing data block".to_string(),
))
}
}
fn parse_delete_data(
&self,
update_str: &str,
prefixes: HashMap<String, NamedNode>,
) -> Result<Update> {
use crate::query::algebra::UpdateOperation;
let data_start = update_str.find('{');
let data_end = update_str.rfind('}');
if let (Some(start), Some(end)) = (data_start, data_end) {
let data_block = update_str[start + 1..end].trim();
let quads = self.parse_quad_data(data_block, &prefixes)?;
Ok(Update {
base: None,
prefixes,
operations: vec![UpdateOperation::DeleteData { data: quads }],
})
} else {
Err(OxirsError::Parse(
"Malformed DELETE DATA: missing data block".to_string(),
))
}
}
fn parse_quad_data(
&self,
data_block: &str,
prefixes: &HashMap<String, NamedNode>,
) -> Result<Vec<Quad>> {
use crate::format::format::RdfFormat;
use crate::format::RdfParser;
use std::io::Cursor;
let mut turtle_doc = String::new();
for (prefix, iri) in prefixes {
turtle_doc.push_str(&format!("@prefix {}: <{}> .\n", prefix, iri.as_str()));
}
turtle_doc.push('\n');
turtle_doc.push_str(data_block);
let parser = RdfParser::new(RdfFormat::Turtle);
let turtle_bytes = turtle_doc.into_bytes();
let cursor = Cursor::new(turtle_bytes);
let quads: Vec<Quad> = parser
.for_reader(cursor)
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|e| OxirsError::Parse(format!("Failed to parse UPDATE data: {}", e)))?;
Ok(quads)
}
fn parse_delete_modify(
&self,
update_str: &str,
prefixes: HashMap<String, NamedNode>,
) -> Result<Update> {
use crate::query::algebra::UpdateOperation;
let delete_pos = update_str.find("DELETE");
let where_pos = update_str.find("WHERE");
if delete_pos.is_none() || where_pos.is_none() {
return Err(OxirsError::Parse(
"Malformed DELETE/WHERE: missing DELETE or WHERE keyword".to_string(),
));
}
let delete_start =
update_str[delete_pos.expect("delete_pos validated as Some above")..].find('{');
let delete_end =
update_str[delete_pos.expect("delete_pos validated as Some above")..].find('}');
if delete_start.is_none() || delete_end.is_none() {
return Err(OxirsError::Parse(
"Malformed DELETE/WHERE: missing template block".to_string(),
));
}
let template_start = delete_pos.expect("delete_pos validated as Some above")
+ delete_start.expect("delete_start validated as Some above")
+ 1;
let template_end = delete_pos.expect("delete_pos validated as Some above")
+ delete_end.expect("delete_end validated as Some above");
let template_block = update_str[template_start..template_end].trim();
let delete_patterns = self.parse_template_patterns(template_block, &prefixes)?;
let where_start =
update_str[where_pos.expect("where_pos validated as Some above")..].find('{');
let where_end =
update_str[where_pos.expect("where_pos validated as Some above")..].rfind('}');
if where_start.is_none() || where_end.is_none() {
return Err(OxirsError::Parse(
"Malformed DELETE/WHERE: missing WHERE pattern block".to_string(),
));
}
let where_pattern_start = where_pos.expect("where_pos validated as Some above")
+ where_start.expect("where_start validated as Some above")
+ 1;
let where_pattern_end = where_pos.expect("where_pos validated as Some above")
+ where_end.expect("where_end validated as Some above");
let where_block = update_str[where_pattern_start..where_pattern_end].trim();
let where_pattern = self.parse_where_pattern(where_block, &prefixes)?;
Ok(Update {
base: None,
prefixes,
operations: vec![UpdateOperation::Modify {
delete: Some(delete_patterns),
insert: None,
where_clause: Box::new(where_pattern),
using: crate::query::algebra::Dataset {
default: vec![],
named: vec![],
},
}],
})
}
fn parse_delete_where(
&self,
update_str: &str,
prefixes: HashMap<String, NamedNode>,
) -> Result<Update> {
use crate::query::algebra::UpdateOperation;
let pattern_start = update_str.find('{');
let pattern_end = update_str.rfind('}');
if let (Some(start), Some(end)) = (pattern_start, pattern_end) {
let pattern_block = update_str[start + 1..end].trim();
let patterns = self.parse_quad_patterns(pattern_block, &prefixes)?;
Ok(Update {
base: None,
prefixes,
operations: vec![UpdateOperation::DeleteWhere { pattern: patterns }],
})
} else {
Err(OxirsError::Parse(
"Malformed DELETE WHERE: missing pattern block".to_string(),
))
}
}
fn parse_insert_where(
&self,
update_str: &str,
prefixes: HashMap<String, NamedNode>,
) -> Result<Update> {
use crate::query::algebra::UpdateOperation;
let insert_pos = update_str.find("INSERT");
let where_pos = update_str.find("WHERE");
if insert_pos.is_none() || where_pos.is_none() {
return Err(OxirsError::Parse(
"Malformed INSERT WHERE: missing INSERT or WHERE keyword".to_string(),
));
}
let insert_start =
update_str[insert_pos.expect("insert_pos validated as Some above")..].find('{');
let insert_end =
update_str[insert_pos.expect("insert_pos validated as Some above")..].find('}');
if insert_start.is_none() || insert_end.is_none() {
return Err(OxirsError::Parse(
"Malformed INSERT WHERE: missing template block".to_string(),
));
}
let template_start = insert_pos.expect("insert_pos validated as Some above")
+ insert_start.expect("insert_start validated as Some above")
+ 1;
let template_end = insert_pos.expect("insert_pos validated as Some above")
+ insert_end.expect("insert_end validated as Some above");
let template_block = update_str[template_start..template_end].trim();
let insert_patterns = self.parse_template_patterns(template_block, &prefixes)?;
let where_start =
update_str[where_pos.expect("where_pos validated as Some above")..].find('{');
let where_end =
update_str[where_pos.expect("where_pos validated as Some above")..].rfind('}');
if where_start.is_none() || where_end.is_none() {
return Err(OxirsError::Parse(
"Malformed INSERT WHERE: missing WHERE pattern block".to_string(),
));
}
let where_pattern_start = where_pos.expect("where_pos validated as Some above")
+ where_start.expect("where_start validated as Some above")
+ 1;
let where_pattern_end = where_pos.expect("where_pos validated as Some above")
+ where_end.expect("where_end validated as Some above");
let where_block = update_str[where_pattern_start..where_pattern_end].trim();
let where_pattern = self.parse_where_pattern(where_block, &prefixes)?;
Ok(Update {
base: None,
prefixes,
operations: vec![UpdateOperation::Modify {
delete: None,
insert: Some(insert_patterns),
where_clause: Box::new(where_pattern),
using: crate::query::algebra::Dataset {
default: vec![],
named: vec![],
},
}],
})
}
fn parse_quad_patterns(
&self,
pattern_block: &str,
prefixes: &HashMap<String, NamedNode>,
) -> Result<Vec<crate::query::algebra::QuadPattern>> {
use crate::format::format::RdfFormat;
use crate::format::RdfParser;
use crate::query::algebra::QuadPattern;
use std::io::Cursor;
let mut turtle_doc = String::new();
for (prefix, iri) in prefixes {
turtle_doc.push_str(&format!("@prefix {}: <{}> .\n", prefix, iri.as_str()));
}
turtle_doc.push('\n');
turtle_doc.push_str(pattern_block);
let parser = RdfParser::new(RdfFormat::Turtle);
let turtle_bytes = turtle_doc.into_bytes();
let cursor = Cursor::new(turtle_bytes);
let quads: Vec<Quad> = parser
.for_reader(cursor)
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|e| OxirsError::Parse(format!("Failed to parse pattern: {}", e)))?;
let quad_patterns: Vec<QuadPattern> = quads
.into_iter()
.map(|quad| QuadPattern {
subject: self.subject_to_term_pattern(quad.subject()),
predicate: self.predicate_to_term_pattern(quad.predicate()),
object: self.object_to_term_pattern(quad.object()),
graph: Some(self.graph_to_term_pattern(quad.graph_name())),
})
.collect();
Ok(quad_patterns)
}
fn parse_template_patterns(
&self,
template_block: &str,
prefixes: &HashMap<String, NamedNode>,
) -> Result<Vec<crate::query::algebra::QuadPattern>> {
use crate::query::algebra::QuadPattern;
let pattern_lines: Vec<&str> = template_block
.split('.')
.map(|s| s.trim())
.filter(|s| !s.is_empty() && *s != "}")
.collect();
let mut quad_patterns = Vec::new();
for line in pattern_lines {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
let subject = self.parse_term_pattern_with_prefix(parts[0], prefixes)?;
let predicate = self.parse_term_pattern_with_prefix(parts[1], prefixes)?;
let object = self.parse_term_pattern_with_prefix(parts[2], prefixes)?;
quad_patterns.push(QuadPattern {
subject,
predicate,
object,
graph: None, });
}
}
Ok(quad_patterns)
}
fn parse_where_pattern(
&self,
pattern_block: &str,
prefixes: &HashMap<String, NamedNode>,
) -> Result<crate::query::algebra::GraphPattern> {
use crate::query::algebra::{AlgebraTriplePattern, GraphPattern};
let pattern_lines: Vec<&str> = pattern_block
.split('.')
.map(|s| s.trim())
.filter(|s| !s.is_empty() && !s.starts_with("FILTER"))
.collect();
let mut triple_patterns = Vec::new();
for line in pattern_lines {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
let subject = self.parse_term_pattern_with_prefix(parts[0], prefixes)?;
let predicate = self.parse_term_pattern_with_prefix(parts[1], prefixes)?;
let object = self.parse_term_pattern_with_prefix(parts[2], prefixes)?;
triple_patterns.push(AlgebraTriplePattern {
subject,
predicate,
object,
});
}
}
Ok(GraphPattern::Bgp(triple_patterns))
}
#[allow(dead_code)]
fn parse_term_pattern(&self, term_str: &str) -> Result<TermPattern> {
let trimmed = term_str.trim();
if let Some(var_name) = trimmed.strip_prefix('?') {
let var = crate::model::Variable::new(var_name)
.map_err(|e| OxirsError::Parse(format!("Invalid variable: {e}")))?;
Ok(TermPattern::Variable(var))
} else if trimmed.starts_with('<') && trimmed.ends_with('>') {
let iri = &trimmed[1..trimmed.len() - 1];
let node =
NamedNode::new(iri).map_err(|e| OxirsError::Parse(format!("Invalid IRI: {e}")))?;
Ok(TermPattern::NamedNode(node))
} else if trimmed.starts_with('"') {
let lit_value = trimmed.trim_matches('"');
Ok(TermPattern::Literal(
crate::model::Literal::new_simple_literal(lit_value),
))
} else {
Err(OxirsError::Parse(format!(
"Cannot parse term pattern: {}",
term_str
)))
}
}
fn parse_term_pattern_with_prefix(
&self,
term_str: &str,
prefixes: &HashMap<String, NamedNode>,
) -> Result<TermPattern> {
let trimmed = term_str.trim();
if let Some(var_name) = trimmed.strip_prefix('?') {
let var = crate::model::Variable::new(var_name)
.map_err(|e| OxirsError::Parse(format!("Invalid variable: {e}")))?;
Ok(TermPattern::Variable(var))
} else if trimmed.starts_with('<') && trimmed.ends_with('>') {
let iri = &trimmed[1..trimmed.len() - 1];
let node =
NamedNode::new(iri).map_err(|e| OxirsError::Parse(format!("Invalid IRI: {e}")))?;
Ok(TermPattern::NamedNode(node))
} else if trimmed.starts_with('"') {
let lit_value = trimmed.trim_matches('"');
Ok(TermPattern::Literal(
crate::model::Literal::new_simple_literal(lit_value),
))
} else if trimmed.contains(':') {
let parts: Vec<&str> = trimmed.splitn(2, ':').collect();
if parts.len() == 2 {
let prefix = parts[0];
let local = parts[1];
if let Some(base_iri) = prefixes.get(prefix) {
let full_iri = format!("{}{}", base_iri.as_str(), local);
let node = NamedNode::new(&full_iri)
.map_err(|e| OxirsError::Parse(format!("Invalid expanded IRI: {e}")))?;
Ok(TermPattern::NamedNode(node))
} else {
Err(OxirsError::Parse(format!("Unknown prefix: {}", prefix)))
}
} else {
Err(OxirsError::Parse(format!(
"Invalid prefixed name: {}",
term_str
)))
}
} else {
Err(OxirsError::Parse(format!(
"Cannot parse term pattern: {}",
term_str
)))
}
}
fn subject_to_term_pattern(&self, subject: &crate::model::Subject) -> TermPattern {
use crate::model::Subject;
match subject {
Subject::NamedNode(n) => TermPattern::NamedNode(n.clone()),
Subject::BlankNode(b) => TermPattern::BlankNode(b.clone()),
Subject::Variable(v) => TermPattern::Variable(v.clone()),
Subject::QuotedTriple(_) => {
TermPattern::Variable(
crate::model::Variable::new("quotedTriple")
.expect("quotedTriple is a valid variable name"),
)
}
}
}
fn predicate_to_term_pattern(&self, predicate: &crate::model::Predicate) -> TermPattern {
use crate::model::Predicate;
match predicate {
Predicate::NamedNode(n) => TermPattern::NamedNode(n.clone()),
Predicate::Variable(v) => TermPattern::Variable(v.clone()),
}
}
fn object_to_term_pattern(&self, object: &crate::model::Object) -> TermPattern {
use crate::model::Object;
match object {
Object::NamedNode(n) => TermPattern::NamedNode(n.clone()),
Object::BlankNode(b) => TermPattern::BlankNode(b.clone()),
Object::Literal(l) => TermPattern::Literal(l.clone()),
Object::Variable(v) => TermPattern::Variable(v.clone()),
Object::QuotedTriple(_) => {
TermPattern::Variable(
crate::model::Variable::new("quotedTripleObj")
.expect("quotedTripleObj is a valid variable name"),
)
}
}
}
fn graph_to_term_pattern(&self, graph: &GraphName) -> TermPattern {
match graph {
GraphName::NamedNode(n) => TermPattern::NamedNode(n.clone()),
GraphName::BlankNode(b) => TermPattern::BlankNode(b.clone()),
GraphName::Variable(v) => TermPattern::Variable(v.clone()),
GraphName::DefaultGraph => {
TermPattern::Variable(
crate::model::Variable::new("defaultGraph")
.expect("defaultGraph is a valid variable name"),
)
}
}
}
fn parse_clear(
&self,
update_str: &str,
prefixes: HashMap<String, NamedNode>,
) -> Result<Update> {
use crate::query::algebra::{GraphTarget, UpdateOperation};
let trimmed = update_str.trim();
let silent = trimmed.contains("SILENT");
let graph_target = if trimmed.contains("DEFAULT") {
GraphTarget::Default
} else if trimmed.contains("ALL") {
GraphTarget::All
} else if let Some(graph_start) = trimmed.find("GRAPH") {
let after_graph = &trimmed[graph_start + 5..].trim();
if let Some(iri_start) = after_graph.find('<') {
if let Some(iri_end) = after_graph.find('>') {
let iri_str = &after_graph[iri_start + 1..iri_end];
let graph_node = NamedNode::new(iri_str)
.map_err(|e| OxirsError::Parse(format!("Invalid graph IRI: {e}")))?;
GraphTarget::Named(graph_node)
} else {
return Err(OxirsError::Parse(
"Malformed graph IRI in CLEAR".to_string(),
));
}
} else {
return Err(OxirsError::Parse("Missing graph IRI in CLEAR".to_string()));
}
} else {
GraphTarget::Default };
Ok(Update {
base: None,
prefixes,
operations: vec![UpdateOperation::Clear {
graph: graph_target,
silent,
}],
})
}
}