1use crate::{
10 model::{GraphName, NamedNode, Quad},
11 query::algebra::{Expression, GraphPattern, GraphTarget, QuadPattern, Update, UpdateOperation},
12 query::{AlgebraTriplePattern, TermPattern},
13 vocab::xsd,
14 OxirsError, Result, Store,
15};
16use std::collections::HashMap;
17
18#[cfg(feature = "parallel")]
19use rayon::prelude::*;
20
21const BATCH_THRESHOLD: usize = 100;
23
24const MAX_BATCH_SIZE: usize = 10_000;
26
27pub struct UpdateExecutor<'a> {
29 store: &'a dyn Store,
30 batch_enabled: bool,
32 batch_size: usize,
34}
35
36impl<'a> UpdateExecutor<'a> {
37 pub fn new(store: &'a dyn Store) -> Self {
39 Self {
40 store,
41 batch_enabled: true,
42 batch_size: BATCH_THRESHOLD,
43 }
44 }
45
46 pub fn with_batch_size(store: &'a dyn Store, batch_size: usize) -> Self {
48 Self {
49 store,
50 batch_enabled: true,
51 batch_size: batch_size.min(MAX_BATCH_SIZE),
52 }
53 }
54
55 pub fn without_batching(store: &'a dyn Store) -> Self {
57 Self {
58 store,
59 batch_enabled: false,
60 batch_size: 1,
61 }
62 }
63
64 pub fn execute(&self, update: &Update) -> Result<()> {
66 for operation in &update.operations {
67 self.execute_operation(operation)?;
68 }
69 Ok(())
70 }
71
72 fn execute_operation(&self, operation: &UpdateOperation) -> Result<()> {
74 match operation {
75 UpdateOperation::InsertData { data } => self.execute_insert_data(data),
76 UpdateOperation::DeleteData { data } => self.execute_delete_data(data),
77 UpdateOperation::DeleteWhere { pattern } => self.execute_delete_where(pattern),
78 UpdateOperation::Modify {
79 delete,
80 insert,
81 where_clause,
82 using: _,
83 } => self.execute_modify(delete, insert, where_clause),
84 UpdateOperation::Load {
85 source,
86 destination,
87 silent,
88 } => self.execute_load(source, destination, *silent),
89 UpdateOperation::Clear { graph, silent } => self.execute_clear(graph, *silent),
90 UpdateOperation::Create { graph, silent } => self.execute_create(graph, *silent),
91 UpdateOperation::Drop { graph, silent } => self.execute_drop(graph, *silent),
92 UpdateOperation::Copy {
93 source,
94 destination,
95 silent,
96 } => self.execute_copy(source, destination, *silent),
97 UpdateOperation::Move {
98 source,
99 destination,
100 silent,
101 } => self.execute_move(source, destination, *silent),
102 UpdateOperation::Add {
103 source,
104 destination,
105 silent,
106 } => self.execute_add(source, destination, *silent),
107 }
108 }
109
110 fn execute_insert_data(&self, data: &[Quad]) -> Result<()> {
112 if !self.batch_enabled || data.len() < self.batch_size {
113 for quad in data {
115 self.store.insert_quad(quad.clone())?;
116 }
117 return Ok(());
118 }
119
120 self.batch_insert_quads(data)
122 }
123
124 fn execute_delete_data(&self, data: &[Quad]) -> Result<()> {
126 if !self.batch_enabled || data.len() < self.batch_size {
127 for quad in data {
129 self.store.remove_quad(quad)?;
130 }
131 return Ok(());
132 }
133
134 self.batch_delete_quads(data)
136 }
137
138 fn batch_insert_quads(&self, quads: &[Quad]) -> Result<()> {
140 for chunk in quads.chunks(self.batch_size) {
142 let batch: Vec<Quad> = chunk.to_vec();
144
145 #[cfg(feature = "parallel")]
146 {
147 if batch.len() > 1000 {
149 let results: Vec<Result<bool>> = batch
150 .par_iter()
151 .map(|quad| self.store.insert_quad(quad.clone()))
152 .collect();
153
154 for result in results {
156 result?;
157 }
158 continue;
159 }
160 }
161
162 for quad in batch {
164 self.store.insert_quad(quad)?;
165 }
166 }
167
168 Ok(())
169 }
170
171 fn batch_delete_quads(&self, quads: &[Quad]) -> Result<()> {
173 for chunk in quads.chunks(self.batch_size) {
175 let batch: Vec<Quad> = chunk.to_vec();
177
178 #[cfg(feature = "parallel")]
179 {
180 if batch.len() > 1000 {
182 let results: Vec<Result<bool>> = batch
183 .par_iter()
184 .map(|quad| self.store.remove_quad(quad))
185 .collect();
186
187 for result in results {
189 result?;
190 }
191 continue;
192 }
193 }
194
195 for quad in &batch {
197 self.store.remove_quad(quad)?;
198 }
199 }
200
201 Ok(())
202 }
203
204 fn execute_delete_where(&self, patterns: &[QuadPattern]) -> Result<()> {
206 let mut all_matching_quads = Vec::new();
208
209 for pattern in patterns {
210 let matching_quads = self.find_matching_quads(pattern)?;
211 all_matching_quads.extend(matching_quads);
212 }
213
214 if self.batch_enabled && all_matching_quads.len() >= self.batch_size {
216 self.batch_delete_quads(&all_matching_quads)
217 } else {
218 for quad in all_matching_quads {
220 self.store.remove_quad(&quad)?;
221 }
222 Ok(())
223 }
224 }
225
226 fn execute_modify(
228 &self,
229 delete_patterns: &Option<Vec<QuadPattern>>,
230 insert_patterns: &Option<Vec<QuadPattern>>,
231 where_clause: &GraphPattern,
232 ) -> Result<()> {
233 let solutions = self.evaluate_graph_pattern(where_clause)?;
235
236 let mut quads_to_delete = Vec::new();
238 let mut quads_to_insert = Vec::new();
239
240 for solution in solutions {
241 if let Some(delete_patterns) = delete_patterns {
243 for pattern in delete_patterns {
244 if let Some(quad) = self.instantiate_quad_pattern(pattern, &solution)? {
245 quads_to_delete.push(quad);
246 }
247 }
248 }
249
250 if let Some(insert_patterns) = insert_patterns {
252 for pattern in insert_patterns {
253 if let Some(quad) = self.instantiate_quad_pattern(pattern, &solution)? {
254 quads_to_insert.push(quad);
255 }
256 }
257 }
258 }
259
260 if !quads_to_delete.is_empty() {
262 if self.batch_enabled && quads_to_delete.len() >= self.batch_size {
263 self.batch_delete_quads(&quads_to_delete)?;
264 } else {
265 for quad in quads_to_delete {
266 self.store.remove_quad(&quad)?;
267 }
268 }
269 }
270
271 if !quads_to_insert.is_empty() {
273 if self.batch_enabled && quads_to_insert.len() >= self.batch_size {
274 self.batch_insert_quads(&quads_to_insert)?;
275 } else {
276 for quad in quads_to_insert {
277 self.store.insert_quad(quad)?;
278 }
279 }
280 }
281
282 Ok(())
283 }
284
285 fn execute_load(
287 &self,
288 source: &NamedNode,
289 _destination: &Option<NamedNode>,
290 _silent: bool,
291 ) -> Result<()> {
292 Err(OxirsError::Update(format!(
295 "LOAD operation not implemented for source: {source}"
296 )))
297 }
298
299 fn execute_clear(&self, graph: &GraphTarget, _silent: bool) -> Result<()> {
301 match graph {
302 GraphTarget::Default => {
303 let default_graph = GraphName::DefaultGraph;
305 let quads = self
306 .store
307 .find_quads(None, None, None, Some(&default_graph))?;
308 for quad in quads {
309 self.store.remove_quad(&quad)?;
310 }
311 }
312 GraphTarget::Named(graph_name) => {
313 let graph = GraphName::NamedNode(graph_name.clone());
315 let quads = self.store.find_quads(None, None, None, Some(&graph))?;
316 for quad in quads {
317 self.store.remove_quad(&quad)?;
318 }
319 }
320 GraphTarget::All => {
321 let quads = self.store.find_quads(None, None, None, None)?;
323 for quad in quads {
324 self.store.remove_quad(&quad)?;
325 }
326 }
327 }
328 Ok(())
329 }
330
331 fn execute_create(&self, _graph: &NamedNode, _silent: bool) -> Result<()> {
333 Ok(())
336 }
337
338 fn execute_drop(&self, graph: &GraphTarget, _silent: bool) -> Result<()> {
340 self.execute_clear(graph, _silent)
343 }
344
345 fn execute_copy(
347 &self,
348 source: &GraphTarget,
349 destination: &GraphTarget,
350 _silent: bool,
351 ) -> Result<()> {
352 self.execute_clear(destination, true)?;
354
355 let source_quads = self.get_quads_from_target(source)?;
356 for quad in source_quads {
357 let dest_quad = self.move_quad_to_target(&quad, destination)?;
358 self.store.insert_quad(dest_quad)?;
359 }
360 Ok(())
361 }
362
363 fn execute_move(
365 &self,
366 source: &GraphTarget,
367 destination: &GraphTarget,
368 _silent: bool,
369 ) -> Result<()> {
370 self.execute_copy(source, destination, true)?;
372 self.execute_drop(source, true)?;
373 Ok(())
374 }
375
376 fn execute_add(
378 &self,
379 source: &GraphTarget,
380 destination: &GraphTarget,
381 _silent: bool,
382 ) -> Result<()> {
383 let source_quads = self.get_quads_from_target(source)?;
385 for quad in source_quads {
386 let dest_quad = self.move_quad_to_target(&quad, destination)?;
387 self.store.insert_quad(dest_quad)?;
388 }
389 Ok(())
390 }
391
392 fn find_matching_quads(&self, pattern: &QuadPattern) -> Result<Vec<Quad>> {
394 let subject = self.term_pattern_to_subject(&pattern.subject)?;
396 let predicate = self.term_pattern_to_predicate(&pattern.predicate)?;
397 let object = self.term_pattern_to_object(&pattern.object)?;
398 let graph = pattern
399 .graph
400 .as_ref()
401 .map(|g| self.term_pattern_to_graph_name(g))
402 .transpose()?;
403
404 self.store.find_quads(
405 subject.as_ref(),
406 predicate.as_ref(),
407 object.as_ref(),
408 graph.as_ref(),
409 )
410 }
411
412 fn term_pattern_to_subject(
414 &self,
415 pattern: &TermPattern,
416 ) -> Result<Option<crate::model::Subject>> {
417 match pattern {
418 TermPattern::NamedNode(n) => Ok(Some(crate::model::Subject::NamedNode(n.clone()))),
419 TermPattern::BlankNode(b) => Ok(Some(crate::model::Subject::BlankNode(b.clone()))),
420 TermPattern::Variable(_) => Ok(None), TermPattern::Literal(_) => Err(OxirsError::Update(
422 "Subject cannot be a literal".to_string(),
423 )),
424 TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
425 "RDF-star quoted triples as subjects not yet fully implemented".to_string(),
426 )),
427 }
428 }
429
430 fn term_pattern_to_predicate(
432 &self,
433 pattern: &TermPattern,
434 ) -> Result<Option<crate::model::Predicate>> {
435 match pattern {
436 TermPattern::NamedNode(n) => Ok(Some(crate::model::Predicate::NamedNode(n.clone()))),
437 TermPattern::Variable(_) => Ok(None), TermPattern::BlankNode(_) | TermPattern::Literal(_) => Err(OxirsError::Update(
439 "Predicate must be a named node".to_string(),
440 )),
441 TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
442 "Quoted triples cannot be predicates".to_string(),
443 )),
444 }
445 }
446
447 fn term_pattern_to_object(
449 &self,
450 pattern: &TermPattern,
451 ) -> Result<Option<crate::model::Object>> {
452 match pattern {
453 TermPattern::NamedNode(n) => Ok(Some(crate::model::Object::NamedNode(n.clone()))),
454 TermPattern::BlankNode(b) => Ok(Some(crate::model::Object::BlankNode(b.clone()))),
455 TermPattern::Literal(l) => Ok(Some(crate::model::Object::Literal(l.clone()))),
456 TermPattern::Variable(_) => Ok(None), TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
458 "RDF-star quoted triples as objects not yet fully implemented".to_string(),
459 )),
460 }
461 }
462
463 fn term_pattern_to_graph_name(&self, pattern: &TermPattern) -> Result<GraphName> {
465 match pattern {
466 TermPattern::NamedNode(n) => Ok(GraphName::NamedNode(n.clone())),
467 TermPattern::Variable(_) => Ok(GraphName::DefaultGraph), TermPattern::BlankNode(_) | TermPattern::Literal(_) => Err(OxirsError::Update(
469 "Graph name must be a named node".to_string(),
470 )),
471 TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
472 "Graph names cannot be quoted triples".to_string(),
473 )),
474 }
475 }
476
477 fn evaluate_graph_pattern(
479 &self,
480 pattern: &GraphPattern,
481 ) -> Result<Vec<HashMap<String, crate::model::Term>>> {
482 use crate::query::{QueryEngine, QueryResult};
483
484 let query_engine = QueryEngine::new();
486
487 let sparql_query = self.graph_pattern_to_sparql(pattern)?;
489
490 match query_engine.query(&sparql_query, self.store)? {
492 QueryResult::Select {
493 variables: _,
494 bindings,
495 } => {
496 let mut solutions = Vec::new();
498 for binding in bindings {
499 let mut solution = HashMap::new();
500 for (var_name, term) in binding {
501 solution.insert(var_name, term);
502 }
503 solutions.push(solution);
504 }
505 Ok(solutions)
506 }
507 _ => Err(OxirsError::Update(
508 "Expected SELECT query result for WHERE clause evaluation".to_string(),
509 )),
510 }
511 }
512
513 fn instantiate_quad_pattern(
515 &self,
516 pattern: &QuadPattern,
517 solution: &HashMap<String, crate::model::Term>,
518 ) -> Result<Option<Quad>> {
519 use crate::model::*;
520
521 let subject = match &pattern.subject {
523 TermPattern::Variable(var) => {
524 if let Some(term) = solution.get(var.name()) {
525 match term {
526 Term::NamedNode(n) => Subject::NamedNode(n.clone()),
527 Term::BlankNode(b) => Subject::BlankNode(b.clone()),
528 _ => return Ok(None), }
530 } else {
531 return Ok(None); }
533 }
534 TermPattern::NamedNode(n) => Subject::NamedNode(n.clone()),
535 TermPattern::BlankNode(b) => Subject::BlankNode(b.clone()),
536 TermPattern::Literal(_) => return Ok(None), TermPattern::QuotedTriple(_) => return Ok(None), };
539
540 let predicate = match &pattern.predicate {
542 TermPattern::Variable(var) => {
543 if let Some(Term::NamedNode(n)) = solution.get(var.name()) {
544 Predicate::NamedNode(n.clone())
545 } else {
546 return Ok(None); }
548 }
549 TermPattern::NamedNode(n) => Predicate::NamedNode(n.clone()),
550 _ => return Ok(None), };
552
553 let object = match &pattern.object {
555 TermPattern::Variable(var) => {
556 if let Some(term) = solution.get(var.name()) {
557 match term {
558 Term::NamedNode(n) => Object::NamedNode(n.clone()),
559 Term::BlankNode(b) => Object::BlankNode(b.clone()),
560 Term::Literal(l) => Object::Literal(l.clone()),
561 _ => return Ok(None), }
563 } else {
564 return Ok(None); }
566 }
567 TermPattern::NamedNode(n) => Object::NamedNode(n.clone()),
568 TermPattern::BlankNode(b) => Object::BlankNode(b.clone()),
569 TermPattern::Literal(l) => Object::Literal(l.clone()),
570 TermPattern::QuotedTriple(_) => return Ok(None), };
572
573 let graph_name = match &pattern.graph {
575 Some(graph_pattern) => match graph_pattern {
576 TermPattern::Variable(var) => {
577 if let Some(Term::NamedNode(n)) = solution.get(var.name()) {
578 GraphName::NamedNode(n.clone())
579 } else {
580 return Ok(None); }
582 }
583 TermPattern::NamedNode(n) => GraphName::NamedNode(n.clone()),
584 _ => return Ok(None), },
586 None => GraphName::DefaultGraph,
587 };
588
589 Ok(Some(Quad::new(subject, predicate, object, graph_name)))
590 }
591
592 fn get_quads_from_target(&self, target: &GraphTarget) -> Result<Vec<Quad>> {
594 match target {
595 GraphTarget::Default => {
596 let graph = GraphName::DefaultGraph;
597 self.store.find_quads(None, None, None, Some(&graph))
598 }
599 GraphTarget::Named(graph_name) => {
600 let graph = GraphName::NamedNode(graph_name.clone());
601 self.store.find_quads(None, None, None, Some(&graph))
602 }
603 GraphTarget::All => self.store.find_quads(None, None, None, None),
604 }
605 }
606
607 fn move_quad_to_target(&self, quad: &Quad, target: &GraphTarget) -> Result<Quad> {
609 match target {
610 GraphTarget::Default => Ok(Quad::new(
611 quad.subject().clone(),
612 quad.predicate().clone(),
613 quad.object().clone(),
614 GraphName::DefaultGraph,
615 )),
616 GraphTarget::Named(graph_name) => Ok(Quad::new(
617 quad.subject().clone(),
618 quad.predicate().clone(),
619 quad.object().clone(),
620 GraphName::NamedNode(graph_name.clone()),
621 )),
622 GraphTarget::All => {
623 Ok(quad.clone())
625 }
626 }
627 }
628
629 fn graph_pattern_to_sparql(&self, pattern: &GraphPattern) -> Result<String> {
631 use crate::query::algebra::*;
632
633 match pattern {
634 GraphPattern::Bgp(triple_patterns) => {
635 let mut sparql = String::from("SELECT * WHERE { ");
636 for (i, triple_pattern) in triple_patterns.iter().enumerate() {
637 if i > 0 {
638 sparql.push_str(" . ");
639 }
640 sparql.push_str(&self.triple_pattern_to_sparql(triple_pattern)?);
641 }
642 sparql.push_str(" }");
643 Ok(sparql)
644 }
645 GraphPattern::Join(left, right) => {
646 let left_sparql = self.graph_pattern_to_sparql(left)?;
647 let right_sparql = self.graph_pattern_to_sparql(right)?;
648
649 let left_where = self.extract_where_clause(&left_sparql)?;
651 let right_where = self.extract_where_clause(&right_sparql)?;
652
653 Ok(format!("SELECT * WHERE {{ {left_where} . {right_where} }}"))
654 }
655 GraphPattern::Filter { expr, inner } => {
656 let inner_sparql = self.graph_pattern_to_sparql(inner)?;
657 let inner_where = self.extract_where_clause(&inner_sparql)?;
658 let filter_expr = self.expression_to_sparql(expr)?;
659
660 Ok(format!(
661 "SELECT * WHERE {{ {inner_where} FILTER ({filter_expr}) }}"
662 ))
663 }
664 GraphPattern::Union(left, right) => {
665 let left_sparql = self.graph_pattern_to_sparql(left)?;
666 let right_sparql = self.graph_pattern_to_sparql(right)?;
667
668 let left_where = self.extract_where_clause(&left_sparql)?;
669 let right_where = self.extract_where_clause(&right_sparql)?;
670
671 Ok(format!(
672 "SELECT * WHERE {{ {{ {left_where} }} UNION {{ {right_where} }} }}"
673 ))
674 }
675 _ => Err(OxirsError::Update(format!(
676 "Graph pattern type not yet supported in SPARQL conversion: {pattern:?}"
677 ))),
678 }
679 }
680
681 fn triple_pattern_to_sparql(&self, pattern: &AlgebraTriplePattern) -> Result<String> {
683 let subject = self.term_pattern_to_sparql(&pattern.subject)?;
684 let predicate = self.term_pattern_to_sparql(&pattern.predicate)?;
685 let object = self.term_pattern_to_sparql(&pattern.object)?;
686
687 Ok(format!("{subject} {predicate} {object}"))
688 }
689
690 fn term_pattern_to_sparql(&self, pattern: &TermPattern) -> Result<String> {
692 match pattern {
693 TermPattern::Variable(var) => Ok(format!("?{}", var.name())),
694 TermPattern::NamedNode(node) => Ok(format!("<{}>", node.as_str())),
695 TermPattern::BlankNode(blank) => Ok(format!("_:{}", blank.as_str())),
696 TermPattern::Literal(literal) => {
697 if let Some(lang) = literal.language() {
698 Ok(format!("\"{}\"@{}", literal.value(), lang))
699 } else if literal.datatype() != xsd::STRING.as_ref() {
700 Ok(format!("\"{}\"^^<{}>", literal.value(), literal.datatype()))
701 } else {
702 Ok(format!("\"{}\"", literal.value()))
703 }
704 }
705 TermPattern::QuotedTriple(_) => Err(OxirsError::Update(
706 "RDF-star quoted triples not yet fully supported in SPARQL conversion".to_string(),
707 )),
708 }
709 }
710
711 #[allow(clippy::only_used_in_recursion)]
713 fn expression_to_sparql(&self, expr: &Expression) -> Result<String> {
714 match expr {
715 Expression::Variable(var) => Ok(format!("?{}", var.name())),
716 Expression::Term(term) => match term {
717 crate::model::Term::NamedNode(n) => Ok(format!("<{}>", n.as_str())),
718 crate::model::Term::BlankNode(b) => Ok(format!("_:{}", b.as_str())),
719 crate::model::Term::Literal(l) => {
720 if let Some(lang) = l.language() {
721 Ok(format!("\"{}\"@{}", l.value(), lang))
722 } else if l.datatype() != xsd::STRING.as_ref() {
723 Ok(format!("\"{}\"^^<{}>", l.value(), l.datatype()))
724 } else {
725 Ok(format!("\"{}\"", l.value()))
726 }
727 }
728 _ => Err(OxirsError::Update(
729 "Unsupported term type in expression".to_string(),
730 )),
731 },
732 Expression::Equal(left, right) => {
733 let left_sparql = self.expression_to_sparql(left)?;
734 let right_sparql = self.expression_to_sparql(right)?;
735 Ok(format!("({left_sparql} = {right_sparql})"))
736 }
737 Expression::And(left, right) => {
738 let left_sparql = self.expression_to_sparql(left)?;
739 let right_sparql = self.expression_to_sparql(right)?;
740 Ok(format!("({left_sparql} && {right_sparql})"))
741 }
742 Expression::Or(left, right) => {
743 let left_sparql = self.expression_to_sparql(left)?;
744 let right_sparql = self.expression_to_sparql(right)?;
745 Ok(format!("({left_sparql} || {right_sparql})"))
746 }
747 Expression::Not(inner) => {
748 let inner_sparql = self.expression_to_sparql(inner)?;
749 Ok(format!("(!{inner_sparql})"))
750 }
751 _ => Err(OxirsError::Update(format!(
752 "Expression type not yet supported in SPARQL conversion: {expr:?}"
753 ))),
754 }
755 }
756
757 fn extract_where_clause(&self, sparql: &str) -> Result<String> {
759 if let Some(start) = sparql.find("WHERE {") {
760 let where_start = start + 7; if let Some(end) = sparql.rfind('}') {
762 let where_content = &sparql[where_start..end].trim();
763 Ok(where_content.to_string())
764 } else {
765 Err(OxirsError::Update(
766 "Malformed SPARQL query: missing closing brace".to_string(),
767 ))
768 }
769 } else {
770 Err(OxirsError::Update(
771 "Malformed SPARQL query: missing WHERE clause".to_string(),
772 ))
773 }
774 }
775}
776
777#[derive(Default)]
779pub struct UpdateParser;
780
781impl UpdateParser {
782 pub fn new() -> Self {
784 Self
785 }
786
787 pub fn parse(&self, update_str: &str) -> Result<Update> {
789 let trimmed = update_str.trim();
793
794 let (prefixes, remaining) = self.extract_prefixes(trimmed)?;
796
797 if remaining.contains("INSERT DATA") {
799 self.parse_insert_data(&remaining, prefixes)
800 } else if remaining.contains("DELETE DATA") {
801 self.parse_delete_data(&remaining, prefixes)
802 } else if self.is_delete_where_shorthand(&remaining) {
803 self.parse_delete_where(&remaining, prefixes)
804 } else if remaining.contains("DELETE") && remaining.contains("WHERE") {
805 self.parse_delete_modify(&remaining, prefixes)
806 } else if remaining.contains("INSERT") && remaining.contains("WHERE") {
807 self.parse_insert_where(&remaining, prefixes)
808 } else if remaining.contains("CLEAR") {
809 self.parse_clear(&remaining, prefixes)
810 } else {
811 Err(OxirsError::Parse(format!(
812 "Unsupported UPDATE operation: {}",
813 remaining
814 )))
815 }
816 }
817
818 fn is_delete_where_shorthand(&self, update_str: &str) -> bool {
820 if let Some(delete_pos) = update_str.find("DELETE") {
823 if let Some(where_pos) = update_str.find("WHERE") {
824 let between = &update_str[delete_pos + 6..where_pos];
825 return !between.contains('{');
827 }
828 }
829 false
830 }
831
832 fn extract_prefixes(&self, update_str: &str) -> Result<(HashMap<String, NamedNode>, String)> {
834 let mut prefixes = HashMap::new();
835 let mut remaining = update_str.to_string();
836
837 loop {
839 let trimmed = remaining.trim();
840
841 if let Some(prefix_start) = trimmed.find("PREFIX") {
842 if prefix_start == 0 || trimmed[..prefix_start].chars().all(|c| c.is_whitespace()) {
844 let after_prefix = &trimmed[prefix_start + 6..];
846
847 if let Some(colon_pos) = after_prefix.find(':') {
848 if let Some(iri_start) = after_prefix.find('<') {
849 if let Some(iri_end) = after_prefix.find('>') {
850 let prefix = after_prefix[..colon_pos].trim().to_string();
851 let iri_str = &after_prefix[iri_start + 1..iri_end];
852 let iri_node = NamedNode::new(iri_str).map_err(|e| {
853 OxirsError::Parse(format!("Invalid prefix IRI: {e}"))
854 })?;
855 prefixes.insert(prefix, iri_node);
856
857 remaining = after_prefix[iri_end + 1..].to_string();
859 continue;
860 }
861 }
862 }
863 }
864 }
865
866 break;
868 }
869
870 Ok((prefixes, remaining.trim().to_string()))
871 }
872
873 fn parse_insert_data(
875 &self,
876 update_str: &str,
877 prefixes: HashMap<String, NamedNode>,
878 ) -> Result<Update> {
879 use crate::query::algebra::UpdateOperation;
880
881 let data_start = update_str.find('{');
883 let data_end = update_str.rfind('}');
884
885 if let (Some(start), Some(end)) = (data_start, data_end) {
886 let data_block = update_str[start + 1..end].trim();
887
888 let quads = self.parse_quad_data(data_block, &prefixes)?;
890
891 Ok(Update {
892 base: None,
893 prefixes,
894 operations: vec![UpdateOperation::InsertData { data: quads }],
895 })
896 } else {
897 Err(OxirsError::Parse(
898 "Malformed INSERT DATA: missing data block".to_string(),
899 ))
900 }
901 }
902
903 fn parse_delete_data(
905 &self,
906 update_str: &str,
907 prefixes: HashMap<String, NamedNode>,
908 ) -> Result<Update> {
909 use crate::query::algebra::UpdateOperation;
910
911 let data_start = update_str.find('{');
913 let data_end = update_str.rfind('}');
914
915 if let (Some(start), Some(end)) = (data_start, data_end) {
916 let data_block = update_str[start + 1..end].trim();
917
918 let quads = self.parse_quad_data(data_block, &prefixes)?;
920
921 Ok(Update {
922 base: None,
923 prefixes,
924 operations: vec![UpdateOperation::DeleteData { data: quads }],
925 })
926 } else {
927 Err(OxirsError::Parse(
928 "Malformed DELETE DATA: missing data block".to_string(),
929 ))
930 }
931 }
932
933 fn parse_quad_data(
935 &self,
936 data_block: &str,
937 prefixes: &HashMap<String, NamedNode>,
938 ) -> Result<Vec<Quad>> {
939 use crate::format::format::RdfFormat;
940 use crate::format::RdfParser;
941 use std::io::Cursor;
942
943 let mut turtle_doc = String::new();
945 for (prefix, iri) in prefixes {
946 turtle_doc.push_str(&format!("@prefix {}: <{}> .\n", prefix, iri.as_str()));
947 }
948 turtle_doc.push('\n');
949 turtle_doc.push_str(data_block);
950
951 if !data_block.trim().is_empty() && !data_block.trim_end().ends_with('.') {
960 turtle_doc.push_str(" .\n");
961 }
962
963 let parser = RdfParser::new(RdfFormat::Turtle);
965 let turtle_bytes = turtle_doc.into_bytes();
966 let cursor = Cursor::new(turtle_bytes);
967
968 let quads: Vec<Quad> = parser
969 .for_reader(cursor)
970 .collect::<std::result::Result<Vec<_>, _>>()
971 .map_err(|e| OxirsError::Parse(format!("Failed to parse UPDATE data: {}", e)))?;
972
973 Ok(quads)
974 }
975
976 fn parse_delete_modify(
978 &self,
979 update_str: &str,
980 prefixes: HashMap<String, NamedNode>,
981 ) -> Result<Update> {
982 use crate::query::algebra::UpdateOperation;
983
984 let delete_pos = update_str.find("DELETE");
986 let where_pos = update_str.find("WHERE");
987
988 if delete_pos.is_none() || where_pos.is_none() {
989 return Err(OxirsError::Parse(
990 "Malformed DELETE/WHERE: missing DELETE or WHERE keyword".to_string(),
991 ));
992 }
993
994 let delete_start =
995 update_str[delete_pos.expect("delete_pos validated as Some above")..].find('{');
996 let delete_end =
997 update_str[delete_pos.expect("delete_pos validated as Some above")..].find('}');
998
999 if delete_start.is_none() || delete_end.is_none() {
1000 return Err(OxirsError::Parse(
1001 "Malformed DELETE/WHERE: missing template block".to_string(),
1002 ));
1003 }
1004
1005 let template_start = delete_pos.expect("delete_pos validated as Some above")
1006 + delete_start.expect("delete_start validated as Some above")
1007 + 1;
1008 let template_end = delete_pos.expect("delete_pos validated as Some above")
1009 + delete_end.expect("delete_end validated as Some above");
1010 let template_block = update_str[template_start..template_end].trim();
1011
1012 let delete_patterns = self.parse_template_patterns(template_block, &prefixes)?;
1014
1015 let where_start =
1017 update_str[where_pos.expect("where_pos validated as Some above")..].find('{');
1018 let where_end =
1019 update_str[where_pos.expect("where_pos validated as Some above")..].rfind('}');
1020
1021 if where_start.is_none() || where_end.is_none() {
1022 return Err(OxirsError::Parse(
1023 "Malformed DELETE/WHERE: missing WHERE pattern block".to_string(),
1024 ));
1025 }
1026
1027 let where_pattern_start = where_pos.expect("where_pos validated as Some above")
1028 + where_start.expect("where_start validated as Some above")
1029 + 1;
1030 let where_pattern_end = where_pos.expect("where_pos validated as Some above")
1031 + where_end.expect("where_end validated as Some above");
1032 let where_block = update_str[where_pattern_start..where_pattern_end].trim();
1033
1034 let where_pattern = self.parse_where_pattern(where_block, &prefixes)?;
1036
1037 Ok(Update {
1038 base: None,
1039 prefixes,
1040 operations: vec![UpdateOperation::Modify {
1041 delete: Some(delete_patterns),
1042 insert: None,
1043 where_clause: Box::new(where_pattern),
1044 using: crate::query::algebra::Dataset {
1045 default: vec![],
1046 named: vec![],
1047 },
1048 }],
1049 })
1050 }
1051
1052 fn parse_delete_where(
1054 &self,
1055 update_str: &str,
1056 prefixes: HashMap<String, NamedNode>,
1057 ) -> Result<Update> {
1058 use crate::query::algebra::UpdateOperation;
1059
1060 let pattern_start = update_str.find('{');
1062 let pattern_end = update_str.rfind('}');
1063
1064 if let (Some(start), Some(end)) = (pattern_start, pattern_end) {
1065 let pattern_block = update_str[start + 1..end].trim();
1066
1067 let patterns = self.parse_quad_patterns(pattern_block, &prefixes)?;
1069
1070 Ok(Update {
1071 base: None,
1072 prefixes,
1073 operations: vec![UpdateOperation::DeleteWhere { pattern: patterns }],
1074 })
1075 } else {
1076 Err(OxirsError::Parse(
1077 "Malformed DELETE WHERE: missing pattern block".to_string(),
1078 ))
1079 }
1080 }
1081
1082 fn parse_insert_where(
1084 &self,
1085 update_str: &str,
1086 prefixes: HashMap<String, NamedNode>,
1087 ) -> Result<Update> {
1088 use crate::query::algebra::UpdateOperation;
1089
1090 let insert_pos = update_str.find("INSERT");
1092 let where_pos = update_str.find("WHERE");
1093
1094 if insert_pos.is_none() || where_pos.is_none() {
1095 return Err(OxirsError::Parse(
1096 "Malformed INSERT WHERE: missing INSERT or WHERE keyword".to_string(),
1097 ));
1098 }
1099
1100 let insert_start =
1101 update_str[insert_pos.expect("insert_pos validated as Some above")..].find('{');
1102 let insert_end =
1103 update_str[insert_pos.expect("insert_pos validated as Some above")..].find('}');
1104
1105 if insert_start.is_none() || insert_end.is_none() {
1106 return Err(OxirsError::Parse(
1107 "Malformed INSERT WHERE: missing template block".to_string(),
1108 ));
1109 }
1110
1111 let template_start = insert_pos.expect("insert_pos validated as Some above")
1112 + insert_start.expect("insert_start validated as Some above")
1113 + 1;
1114 let template_end = insert_pos.expect("insert_pos validated as Some above")
1115 + insert_end.expect("insert_end validated as Some above");
1116 let template_block = update_str[template_start..template_end].trim();
1117
1118 let insert_patterns = self.parse_template_patterns(template_block, &prefixes)?;
1120
1121 let where_start =
1123 update_str[where_pos.expect("where_pos validated as Some above")..].find('{');
1124 let where_end =
1125 update_str[where_pos.expect("where_pos validated as Some above")..].rfind('}');
1126
1127 if where_start.is_none() || where_end.is_none() {
1128 return Err(OxirsError::Parse(
1129 "Malformed INSERT WHERE: missing WHERE pattern block".to_string(),
1130 ));
1131 }
1132
1133 let where_pattern_start = where_pos.expect("where_pos validated as Some above")
1134 + where_start.expect("where_start validated as Some above")
1135 + 1;
1136 let where_pattern_end = where_pos.expect("where_pos validated as Some above")
1137 + where_end.expect("where_end validated as Some above");
1138 let where_block = update_str[where_pattern_start..where_pattern_end].trim();
1139
1140 let where_pattern = self.parse_where_pattern(where_block, &prefixes)?;
1142
1143 Ok(Update {
1144 base: None,
1145 prefixes,
1146 operations: vec![UpdateOperation::Modify {
1147 delete: None,
1148 insert: Some(insert_patterns),
1149 where_clause: Box::new(where_pattern),
1150 using: crate::query::algebra::Dataset {
1151 default: vec![],
1152 named: vec![],
1153 },
1154 }],
1155 })
1156 }
1157
1158 fn parse_quad_patterns(
1160 &self,
1161 pattern_block: &str,
1162 prefixes: &HashMap<String, NamedNode>,
1163 ) -> Result<Vec<crate::query::algebra::QuadPattern>> {
1164 use crate::format::format::RdfFormat;
1165 use crate::format::RdfParser;
1166 use crate::query::algebra::QuadPattern;
1167 use std::io::Cursor;
1168
1169 let mut turtle_doc = String::new();
1171 for (prefix, iri) in prefixes {
1172 turtle_doc.push_str(&format!("@prefix {}: <{}> .\n", prefix, iri.as_str()));
1173 }
1174 turtle_doc.push('\n');
1175 turtle_doc.push_str(pattern_block);
1176
1177 let parser = RdfParser::new(RdfFormat::Turtle);
1179 let turtle_bytes = turtle_doc.into_bytes();
1180 let cursor = Cursor::new(turtle_bytes);
1181
1182 let quads: Vec<Quad> = parser
1183 .for_reader(cursor)
1184 .collect::<std::result::Result<Vec<_>, _>>()
1185 .map_err(|e| OxirsError::Parse(format!("Failed to parse pattern: {}", e)))?;
1186
1187 let quad_patterns: Vec<QuadPattern> = quads
1189 .into_iter()
1190 .map(|quad| QuadPattern {
1191 subject: self.subject_to_term_pattern(quad.subject()),
1192 predicate: self.predicate_to_term_pattern(quad.predicate()),
1193 object: self.object_to_term_pattern(quad.object()),
1194 graph: Some(self.graph_to_term_pattern(quad.graph_name())),
1195 })
1196 .collect();
1197
1198 Ok(quad_patterns)
1199 }
1200
1201 fn parse_template_patterns(
1203 &self,
1204 template_block: &str,
1205 prefixes: &HashMap<String, NamedNode>,
1206 ) -> Result<Vec<crate::query::algebra::QuadPattern>> {
1207 use crate::query::algebra::QuadPattern;
1208
1209 let pattern_lines: Vec<&str> = template_block
1211 .split('.')
1212 .map(|s| s.trim())
1213 .filter(|s| !s.is_empty() && *s != "}")
1214 .collect();
1215
1216 let mut quad_patterns = Vec::new();
1217
1218 for line in pattern_lines {
1219 let parts: Vec<&str> = line.split_whitespace().collect();
1221 if parts.len() >= 3 {
1222 let subject = self.parse_term_pattern_with_prefix(parts[0], prefixes)?;
1223 let predicate = self.parse_term_pattern_with_prefix(parts[1], prefixes)?;
1224 let object = self.parse_term_pattern_with_prefix(parts[2], prefixes)?;
1225
1226 quad_patterns.push(QuadPattern {
1227 subject,
1228 predicate,
1229 object,
1230 graph: None, });
1232 }
1233 }
1234
1235 Ok(quad_patterns)
1236 }
1237
1238 fn parse_where_pattern(
1240 &self,
1241 pattern_block: &str,
1242 prefixes: &HashMap<String, NamedNode>,
1243 ) -> Result<crate::query::algebra::GraphPattern> {
1244 use crate::query::algebra::{AlgebraTriplePattern, GraphPattern};
1245
1246 let pattern_lines: Vec<&str> = pattern_block
1251 .split('.')
1252 .map(|s| s.trim())
1253 .filter(|s| !s.is_empty() && !s.starts_with("FILTER"))
1254 .collect();
1255
1256 let mut triple_patterns = Vec::new();
1257
1258 for line in pattern_lines {
1259 let parts: Vec<&str> = line.split_whitespace().collect();
1261 if parts.len() >= 3 {
1262 let subject = self.parse_term_pattern_with_prefix(parts[0], prefixes)?;
1263 let predicate = self.parse_term_pattern_with_prefix(parts[1], prefixes)?;
1264 let object = self.parse_term_pattern_with_prefix(parts[2], prefixes)?;
1265
1266 triple_patterns.push(AlgebraTriplePattern {
1267 subject,
1268 predicate,
1269 object,
1270 });
1271 }
1272 }
1273
1274 Ok(GraphPattern::Bgp(triple_patterns))
1275 }
1276
1277 #[allow(dead_code)]
1279 fn parse_term_pattern(&self, term_str: &str) -> Result<TermPattern> {
1280 let trimmed = term_str.trim();
1281
1282 if let Some(var_name) = trimmed.strip_prefix('?') {
1283 let var = crate::model::Variable::new(var_name)
1285 .map_err(|e| OxirsError::Parse(format!("Invalid variable: {e}")))?;
1286 Ok(TermPattern::Variable(var))
1287 } else if trimmed.starts_with('<') && trimmed.ends_with('>') {
1288 let iri = &trimmed[1..trimmed.len() - 1];
1290 let node =
1291 NamedNode::new(iri).map_err(|e| OxirsError::Parse(format!("Invalid IRI: {e}")))?;
1292 Ok(TermPattern::NamedNode(node))
1293 } else if trimmed.starts_with('"') {
1294 let lit_value = trimmed.trim_matches('"');
1297 Ok(TermPattern::Literal(
1298 crate::model::Literal::new_simple_literal(lit_value),
1299 ))
1300 } else {
1301 Err(OxirsError::Parse(format!(
1302 "Cannot parse term pattern: {}",
1303 term_str
1304 )))
1305 }
1306 }
1307
1308 fn parse_term_pattern_with_prefix(
1310 &self,
1311 term_str: &str,
1312 prefixes: &HashMap<String, NamedNode>,
1313 ) -> Result<TermPattern> {
1314 let trimmed = term_str.trim();
1315
1316 if let Some(var_name) = trimmed.strip_prefix('?') {
1317 let var = crate::model::Variable::new(var_name)
1319 .map_err(|e| OxirsError::Parse(format!("Invalid variable: {e}")))?;
1320 Ok(TermPattern::Variable(var))
1321 } else if trimmed.starts_with('<') && trimmed.ends_with('>') {
1322 let iri = &trimmed[1..trimmed.len() - 1];
1324 let node =
1325 NamedNode::new(iri).map_err(|e| OxirsError::Parse(format!("Invalid IRI: {e}")))?;
1326 Ok(TermPattern::NamedNode(node))
1327 } else if trimmed.starts_with('"') {
1328 let lit_value = trimmed.trim_matches('"');
1331 Ok(TermPattern::Literal(
1332 crate::model::Literal::new_simple_literal(lit_value),
1333 ))
1334 } else if trimmed.contains(':') {
1335 let parts: Vec<&str> = trimmed.splitn(2, ':').collect();
1337 if parts.len() == 2 {
1338 let prefix = parts[0];
1339 let local = parts[1];
1340
1341 if let Some(base_iri) = prefixes.get(prefix) {
1342 let full_iri = format!("{}{}", base_iri.as_str(), local);
1344 let node = NamedNode::new(&full_iri)
1345 .map_err(|e| OxirsError::Parse(format!("Invalid expanded IRI: {e}")))?;
1346 Ok(TermPattern::NamedNode(node))
1347 } else {
1348 Err(OxirsError::Parse(format!("Unknown prefix: {}", prefix)))
1349 }
1350 } else {
1351 Err(OxirsError::Parse(format!(
1352 "Invalid prefixed name: {}",
1353 term_str
1354 )))
1355 }
1356 } else {
1357 Err(OxirsError::Parse(format!(
1358 "Cannot parse term pattern: {}",
1359 term_str
1360 )))
1361 }
1362 }
1363
1364 fn subject_to_term_pattern(&self, subject: &crate::model::Subject) -> TermPattern {
1366 use crate::model::Subject;
1367 match subject {
1368 Subject::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1369 Subject::BlankNode(b) => TermPattern::BlankNode(b.clone()),
1370 Subject::Variable(v) => TermPattern::Variable(v.clone()),
1371 Subject::QuotedTriple(_) => {
1372 TermPattern::Variable(
1374 crate::model::Variable::new("quotedTriple")
1375 .expect("quotedTriple is a valid variable name"),
1376 )
1377 }
1378 }
1379 }
1380
1381 fn predicate_to_term_pattern(&self, predicate: &crate::model::Predicate) -> TermPattern {
1383 use crate::model::Predicate;
1384 match predicate {
1385 Predicate::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1386 Predicate::Variable(v) => TermPattern::Variable(v.clone()),
1387 }
1388 }
1389
1390 fn object_to_term_pattern(&self, object: &crate::model::Object) -> TermPattern {
1392 use crate::model::Object;
1393 match object {
1394 Object::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1395 Object::BlankNode(b) => TermPattern::BlankNode(b.clone()),
1396 Object::Literal(l) => TermPattern::Literal(l.clone()),
1397 Object::Variable(v) => TermPattern::Variable(v.clone()),
1398 Object::QuotedTriple(_) => {
1399 TermPattern::Variable(
1401 crate::model::Variable::new("quotedTripleObj")
1402 .expect("quotedTripleObj is a valid variable name"),
1403 )
1404 }
1405 }
1406 }
1407
1408 fn graph_to_term_pattern(&self, graph: &GraphName) -> TermPattern {
1410 match graph {
1411 GraphName::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1412 GraphName::BlankNode(b) => TermPattern::BlankNode(b.clone()),
1413 GraphName::Variable(v) => TermPattern::Variable(v.clone()),
1414 GraphName::DefaultGraph => {
1415 TermPattern::Variable(
1417 crate::model::Variable::new("defaultGraph")
1418 .expect("defaultGraph is a valid variable name"),
1419 )
1420 }
1421 }
1422 }
1423
1424 fn parse_clear(
1426 &self,
1427 update_str: &str,
1428 prefixes: HashMap<String, NamedNode>,
1429 ) -> Result<Update> {
1430 use crate::query::algebra::{GraphTarget, UpdateOperation};
1431
1432 let trimmed = update_str.trim();
1433 let silent = trimmed.contains("SILENT");
1434
1435 let graph_target = if trimmed.contains("DEFAULT") {
1436 GraphTarget::Default
1437 } else if trimmed.contains("ALL") {
1438 GraphTarget::All
1439 } else if let Some(graph_start) = trimmed.find("GRAPH") {
1440 let after_graph = &trimmed[graph_start + 5..].trim();
1442 if let Some(iri_start) = after_graph.find('<') {
1443 if let Some(iri_end) = after_graph.find('>') {
1444 let iri_str = &after_graph[iri_start + 1..iri_end];
1445 let graph_node = NamedNode::new(iri_str)
1446 .map_err(|e| OxirsError::Parse(format!("Invalid graph IRI: {e}")))?;
1447 GraphTarget::Named(graph_node)
1448 } else {
1449 return Err(OxirsError::Parse(
1450 "Malformed graph IRI in CLEAR".to_string(),
1451 ));
1452 }
1453 } else {
1454 return Err(OxirsError::Parse("Missing graph IRI in CLEAR".to_string()));
1455 }
1456 } else {
1457 GraphTarget::Default };
1459
1460 Ok(Update {
1461 base: None,
1462 prefixes,
1463 operations: vec![UpdateOperation::Clear {
1464 graph: graph_target,
1465 silent,
1466 }],
1467 })
1468 }
1469}