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 let parser = RdfParser::new(RdfFormat::Turtle);
953 let turtle_bytes = turtle_doc.into_bytes();
954 let cursor = Cursor::new(turtle_bytes);
955
956 let quads: Vec<Quad> = parser
957 .for_reader(cursor)
958 .collect::<std::result::Result<Vec<_>, _>>()
959 .map_err(|e| OxirsError::Parse(format!("Failed to parse UPDATE data: {}", e)))?;
960
961 Ok(quads)
962 }
963
964 fn parse_delete_modify(
966 &self,
967 update_str: &str,
968 prefixes: HashMap<String, NamedNode>,
969 ) -> Result<Update> {
970 use crate::query::algebra::UpdateOperation;
971
972 let delete_pos = update_str.find("DELETE");
974 let where_pos = update_str.find("WHERE");
975
976 if delete_pos.is_none() || where_pos.is_none() {
977 return Err(OxirsError::Parse(
978 "Malformed DELETE/WHERE: missing DELETE or WHERE keyword".to_string(),
979 ));
980 }
981
982 let delete_start =
983 update_str[delete_pos.expect("delete_pos validated as Some above")..].find('{');
984 let delete_end =
985 update_str[delete_pos.expect("delete_pos validated as Some above")..].find('}');
986
987 if delete_start.is_none() || delete_end.is_none() {
988 return Err(OxirsError::Parse(
989 "Malformed DELETE/WHERE: missing template block".to_string(),
990 ));
991 }
992
993 let template_start = delete_pos.expect("delete_pos validated as Some above")
994 + delete_start.expect("delete_start validated as Some above")
995 + 1;
996 let template_end = delete_pos.expect("delete_pos validated as Some above")
997 + delete_end.expect("delete_end validated as Some above");
998 let template_block = update_str[template_start..template_end].trim();
999
1000 let delete_patterns = self.parse_template_patterns(template_block, &prefixes)?;
1002
1003 let where_start =
1005 update_str[where_pos.expect("where_pos validated as Some above")..].find('{');
1006 let where_end =
1007 update_str[where_pos.expect("where_pos validated as Some above")..].rfind('}');
1008
1009 if where_start.is_none() || where_end.is_none() {
1010 return Err(OxirsError::Parse(
1011 "Malformed DELETE/WHERE: missing WHERE pattern block".to_string(),
1012 ));
1013 }
1014
1015 let where_pattern_start = where_pos.expect("where_pos validated as Some above")
1016 + where_start.expect("where_start validated as Some above")
1017 + 1;
1018 let where_pattern_end = where_pos.expect("where_pos validated as Some above")
1019 + where_end.expect("where_end validated as Some above");
1020 let where_block = update_str[where_pattern_start..where_pattern_end].trim();
1021
1022 let where_pattern = self.parse_where_pattern(where_block, &prefixes)?;
1024
1025 Ok(Update {
1026 base: None,
1027 prefixes,
1028 operations: vec![UpdateOperation::Modify {
1029 delete: Some(delete_patterns),
1030 insert: None,
1031 where_clause: Box::new(where_pattern),
1032 using: crate::query::algebra::Dataset {
1033 default: vec![],
1034 named: vec![],
1035 },
1036 }],
1037 })
1038 }
1039
1040 fn parse_delete_where(
1042 &self,
1043 update_str: &str,
1044 prefixes: HashMap<String, NamedNode>,
1045 ) -> Result<Update> {
1046 use crate::query::algebra::UpdateOperation;
1047
1048 let pattern_start = update_str.find('{');
1050 let pattern_end = update_str.rfind('}');
1051
1052 if let (Some(start), Some(end)) = (pattern_start, pattern_end) {
1053 let pattern_block = update_str[start + 1..end].trim();
1054
1055 let patterns = self.parse_quad_patterns(pattern_block, &prefixes)?;
1057
1058 Ok(Update {
1059 base: None,
1060 prefixes,
1061 operations: vec![UpdateOperation::DeleteWhere { pattern: patterns }],
1062 })
1063 } else {
1064 Err(OxirsError::Parse(
1065 "Malformed DELETE WHERE: missing pattern block".to_string(),
1066 ))
1067 }
1068 }
1069
1070 fn parse_insert_where(
1072 &self,
1073 update_str: &str,
1074 prefixes: HashMap<String, NamedNode>,
1075 ) -> Result<Update> {
1076 use crate::query::algebra::UpdateOperation;
1077
1078 let insert_pos = update_str.find("INSERT");
1080 let where_pos = update_str.find("WHERE");
1081
1082 if insert_pos.is_none() || where_pos.is_none() {
1083 return Err(OxirsError::Parse(
1084 "Malformed INSERT WHERE: missing INSERT or WHERE keyword".to_string(),
1085 ));
1086 }
1087
1088 let insert_start =
1089 update_str[insert_pos.expect("insert_pos validated as Some above")..].find('{');
1090 let insert_end =
1091 update_str[insert_pos.expect("insert_pos validated as Some above")..].find('}');
1092
1093 if insert_start.is_none() || insert_end.is_none() {
1094 return Err(OxirsError::Parse(
1095 "Malformed INSERT WHERE: missing template block".to_string(),
1096 ));
1097 }
1098
1099 let template_start = insert_pos.expect("insert_pos validated as Some above")
1100 + insert_start.expect("insert_start validated as Some above")
1101 + 1;
1102 let template_end = insert_pos.expect("insert_pos validated as Some above")
1103 + insert_end.expect("insert_end validated as Some above");
1104 let template_block = update_str[template_start..template_end].trim();
1105
1106 let insert_patterns = self.parse_template_patterns(template_block, &prefixes)?;
1108
1109 let where_start =
1111 update_str[where_pos.expect("where_pos validated as Some above")..].find('{');
1112 let where_end =
1113 update_str[where_pos.expect("where_pos validated as Some above")..].rfind('}');
1114
1115 if where_start.is_none() || where_end.is_none() {
1116 return Err(OxirsError::Parse(
1117 "Malformed INSERT WHERE: missing WHERE pattern block".to_string(),
1118 ));
1119 }
1120
1121 let where_pattern_start = where_pos.expect("where_pos validated as Some above")
1122 + where_start.expect("where_start validated as Some above")
1123 + 1;
1124 let where_pattern_end = where_pos.expect("where_pos validated as Some above")
1125 + where_end.expect("where_end validated as Some above");
1126 let where_block = update_str[where_pattern_start..where_pattern_end].trim();
1127
1128 let where_pattern = self.parse_where_pattern(where_block, &prefixes)?;
1130
1131 Ok(Update {
1132 base: None,
1133 prefixes,
1134 operations: vec![UpdateOperation::Modify {
1135 delete: None,
1136 insert: Some(insert_patterns),
1137 where_clause: Box::new(where_pattern),
1138 using: crate::query::algebra::Dataset {
1139 default: vec![],
1140 named: vec![],
1141 },
1142 }],
1143 })
1144 }
1145
1146 fn parse_quad_patterns(
1148 &self,
1149 pattern_block: &str,
1150 prefixes: &HashMap<String, NamedNode>,
1151 ) -> Result<Vec<crate::query::algebra::QuadPattern>> {
1152 use crate::format::format::RdfFormat;
1153 use crate::format::RdfParser;
1154 use crate::query::algebra::QuadPattern;
1155 use std::io::Cursor;
1156
1157 let mut turtle_doc = String::new();
1159 for (prefix, iri) in prefixes {
1160 turtle_doc.push_str(&format!("@prefix {}: <{}> .\n", prefix, iri.as_str()));
1161 }
1162 turtle_doc.push('\n');
1163 turtle_doc.push_str(pattern_block);
1164
1165 let parser = RdfParser::new(RdfFormat::Turtle);
1167 let turtle_bytes = turtle_doc.into_bytes();
1168 let cursor = Cursor::new(turtle_bytes);
1169
1170 let quads: Vec<Quad> = parser
1171 .for_reader(cursor)
1172 .collect::<std::result::Result<Vec<_>, _>>()
1173 .map_err(|e| OxirsError::Parse(format!("Failed to parse pattern: {}", e)))?;
1174
1175 let quad_patterns: Vec<QuadPattern> = quads
1177 .into_iter()
1178 .map(|quad| QuadPattern {
1179 subject: self.subject_to_term_pattern(quad.subject()),
1180 predicate: self.predicate_to_term_pattern(quad.predicate()),
1181 object: self.object_to_term_pattern(quad.object()),
1182 graph: Some(self.graph_to_term_pattern(quad.graph_name())),
1183 })
1184 .collect();
1185
1186 Ok(quad_patterns)
1187 }
1188
1189 fn parse_template_patterns(
1191 &self,
1192 template_block: &str,
1193 prefixes: &HashMap<String, NamedNode>,
1194 ) -> Result<Vec<crate::query::algebra::QuadPattern>> {
1195 use crate::query::algebra::QuadPattern;
1196
1197 let pattern_lines: Vec<&str> = template_block
1199 .split('.')
1200 .map(|s| s.trim())
1201 .filter(|s| !s.is_empty() && *s != "}")
1202 .collect();
1203
1204 let mut quad_patterns = Vec::new();
1205
1206 for line in pattern_lines {
1207 let parts: Vec<&str> = line.split_whitespace().collect();
1209 if parts.len() >= 3 {
1210 let subject = self.parse_term_pattern_with_prefix(parts[0], prefixes)?;
1211 let predicate = self.parse_term_pattern_with_prefix(parts[1], prefixes)?;
1212 let object = self.parse_term_pattern_with_prefix(parts[2], prefixes)?;
1213
1214 quad_patterns.push(QuadPattern {
1215 subject,
1216 predicate,
1217 object,
1218 graph: None, });
1220 }
1221 }
1222
1223 Ok(quad_patterns)
1224 }
1225
1226 fn parse_where_pattern(
1228 &self,
1229 pattern_block: &str,
1230 prefixes: &HashMap<String, NamedNode>,
1231 ) -> Result<crate::query::algebra::GraphPattern> {
1232 use crate::query::algebra::{AlgebraTriplePattern, GraphPattern};
1233
1234 let pattern_lines: Vec<&str> = pattern_block
1239 .split('.')
1240 .map(|s| s.trim())
1241 .filter(|s| !s.is_empty() && !s.starts_with("FILTER"))
1242 .collect();
1243
1244 let mut triple_patterns = Vec::new();
1245
1246 for line in pattern_lines {
1247 let parts: Vec<&str> = line.split_whitespace().collect();
1249 if parts.len() >= 3 {
1250 let subject = self.parse_term_pattern_with_prefix(parts[0], prefixes)?;
1251 let predicate = self.parse_term_pattern_with_prefix(parts[1], prefixes)?;
1252 let object = self.parse_term_pattern_with_prefix(parts[2], prefixes)?;
1253
1254 triple_patterns.push(AlgebraTriplePattern {
1255 subject,
1256 predicate,
1257 object,
1258 });
1259 }
1260 }
1261
1262 Ok(GraphPattern::Bgp(triple_patterns))
1263 }
1264
1265 #[allow(dead_code)]
1267 fn parse_term_pattern(&self, term_str: &str) -> Result<TermPattern> {
1268 let trimmed = term_str.trim();
1269
1270 if let Some(var_name) = trimmed.strip_prefix('?') {
1271 let var = crate::model::Variable::new(var_name)
1273 .map_err(|e| OxirsError::Parse(format!("Invalid variable: {e}")))?;
1274 Ok(TermPattern::Variable(var))
1275 } else if trimmed.starts_with('<') && trimmed.ends_with('>') {
1276 let iri = &trimmed[1..trimmed.len() - 1];
1278 let node =
1279 NamedNode::new(iri).map_err(|e| OxirsError::Parse(format!("Invalid IRI: {e}")))?;
1280 Ok(TermPattern::NamedNode(node))
1281 } else if trimmed.starts_with('"') {
1282 let lit_value = trimmed.trim_matches('"');
1285 Ok(TermPattern::Literal(
1286 crate::model::Literal::new_simple_literal(lit_value),
1287 ))
1288 } else {
1289 Err(OxirsError::Parse(format!(
1290 "Cannot parse term pattern: {}",
1291 term_str
1292 )))
1293 }
1294 }
1295
1296 fn parse_term_pattern_with_prefix(
1298 &self,
1299 term_str: &str,
1300 prefixes: &HashMap<String, NamedNode>,
1301 ) -> Result<TermPattern> {
1302 let trimmed = term_str.trim();
1303
1304 if let Some(var_name) = trimmed.strip_prefix('?') {
1305 let var = crate::model::Variable::new(var_name)
1307 .map_err(|e| OxirsError::Parse(format!("Invalid variable: {e}")))?;
1308 Ok(TermPattern::Variable(var))
1309 } else if trimmed.starts_with('<') && trimmed.ends_with('>') {
1310 let iri = &trimmed[1..trimmed.len() - 1];
1312 let node =
1313 NamedNode::new(iri).map_err(|e| OxirsError::Parse(format!("Invalid IRI: {e}")))?;
1314 Ok(TermPattern::NamedNode(node))
1315 } else if trimmed.starts_with('"') {
1316 let lit_value = trimmed.trim_matches('"');
1319 Ok(TermPattern::Literal(
1320 crate::model::Literal::new_simple_literal(lit_value),
1321 ))
1322 } else if trimmed.contains(':') {
1323 let parts: Vec<&str> = trimmed.splitn(2, ':').collect();
1325 if parts.len() == 2 {
1326 let prefix = parts[0];
1327 let local = parts[1];
1328
1329 if let Some(base_iri) = prefixes.get(prefix) {
1330 let full_iri = format!("{}{}", base_iri.as_str(), local);
1332 let node = NamedNode::new(&full_iri)
1333 .map_err(|e| OxirsError::Parse(format!("Invalid expanded IRI: {e}")))?;
1334 Ok(TermPattern::NamedNode(node))
1335 } else {
1336 Err(OxirsError::Parse(format!("Unknown prefix: {}", prefix)))
1337 }
1338 } else {
1339 Err(OxirsError::Parse(format!(
1340 "Invalid prefixed name: {}",
1341 term_str
1342 )))
1343 }
1344 } else {
1345 Err(OxirsError::Parse(format!(
1346 "Cannot parse term pattern: {}",
1347 term_str
1348 )))
1349 }
1350 }
1351
1352 fn subject_to_term_pattern(&self, subject: &crate::model::Subject) -> TermPattern {
1354 use crate::model::Subject;
1355 match subject {
1356 Subject::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1357 Subject::BlankNode(b) => TermPattern::BlankNode(b.clone()),
1358 Subject::Variable(v) => TermPattern::Variable(v.clone()),
1359 Subject::QuotedTriple(_) => {
1360 TermPattern::Variable(
1362 crate::model::Variable::new("quotedTriple")
1363 .expect("quotedTriple is a valid variable name"),
1364 )
1365 }
1366 }
1367 }
1368
1369 fn predicate_to_term_pattern(&self, predicate: &crate::model::Predicate) -> TermPattern {
1371 use crate::model::Predicate;
1372 match predicate {
1373 Predicate::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1374 Predicate::Variable(v) => TermPattern::Variable(v.clone()),
1375 }
1376 }
1377
1378 fn object_to_term_pattern(&self, object: &crate::model::Object) -> TermPattern {
1380 use crate::model::Object;
1381 match object {
1382 Object::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1383 Object::BlankNode(b) => TermPattern::BlankNode(b.clone()),
1384 Object::Literal(l) => TermPattern::Literal(l.clone()),
1385 Object::Variable(v) => TermPattern::Variable(v.clone()),
1386 Object::QuotedTriple(_) => {
1387 TermPattern::Variable(
1389 crate::model::Variable::new("quotedTripleObj")
1390 .expect("quotedTripleObj is a valid variable name"),
1391 )
1392 }
1393 }
1394 }
1395
1396 fn graph_to_term_pattern(&self, graph: &GraphName) -> TermPattern {
1398 match graph {
1399 GraphName::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1400 GraphName::BlankNode(b) => TermPattern::BlankNode(b.clone()),
1401 GraphName::Variable(v) => TermPattern::Variable(v.clone()),
1402 GraphName::DefaultGraph => {
1403 TermPattern::Variable(
1405 crate::model::Variable::new("defaultGraph")
1406 .expect("defaultGraph is a valid variable name"),
1407 )
1408 }
1409 }
1410 }
1411
1412 fn parse_clear(
1414 &self,
1415 update_str: &str,
1416 prefixes: HashMap<String, NamedNode>,
1417 ) -> Result<Update> {
1418 use crate::query::algebra::{GraphTarget, UpdateOperation};
1419
1420 let trimmed = update_str.trim();
1421 let silent = trimmed.contains("SILENT");
1422
1423 let graph_target = if trimmed.contains("DEFAULT") {
1424 GraphTarget::Default
1425 } else if trimmed.contains("ALL") {
1426 GraphTarget::All
1427 } else if let Some(graph_start) = trimmed.find("GRAPH") {
1428 let after_graph = &trimmed[graph_start + 5..].trim();
1430 if let Some(iri_start) = after_graph.find('<') {
1431 if let Some(iri_end) = after_graph.find('>') {
1432 let iri_str = &after_graph[iri_start + 1..iri_end];
1433 let graph_node = NamedNode::new(iri_str)
1434 .map_err(|e| OxirsError::Parse(format!("Invalid graph IRI: {e}")))?;
1435 GraphTarget::Named(graph_node)
1436 } else {
1437 return Err(OxirsError::Parse(
1438 "Malformed graph IRI in CLEAR".to_string(),
1439 ));
1440 }
1441 } else {
1442 return Err(OxirsError::Parse("Missing graph IRI in CLEAR".to_string()));
1443 }
1444 } else {
1445 GraphTarget::Default };
1447
1448 Ok(Update {
1449 base: None,
1450 prefixes,
1451 operations: vec![UpdateOperation::Clear {
1452 graph: graph_target,
1453 silent,
1454 }],
1455 })
1456 }
1457}