oxirs_core/query/
update.rs

1//! SPARQL UPDATE execution engine
2
3use crate::{
4    model::{GraphName, NamedNode, Quad},
5    query::algebra::{Expression, GraphPattern, GraphTarget, QuadPattern, Update, UpdateOperation},
6    query::{AlgebraTriplePattern, TermPattern},
7    vocab::xsd,
8    OxirsError, Result, Store,
9};
10use std::collections::HashMap;
11
12/// SPARQL UPDATE executor
13pub struct UpdateExecutor<'a> {
14    store: &'a dyn Store,
15}
16
17impl<'a> UpdateExecutor<'a> {
18    /// Create a new update executor
19    pub fn new(store: &'a dyn Store) -> Self {
20        Self { store }
21    }
22
23    /// Execute a SPARQL UPDATE request
24    pub fn execute(&self, update: &Update) -> Result<()> {
25        for operation in &update.operations {
26            self.execute_operation(operation)?;
27        }
28        Ok(())
29    }
30
31    /// Execute a single update operation
32    fn execute_operation(&self, operation: &UpdateOperation) -> Result<()> {
33        match operation {
34            UpdateOperation::InsertData { data } => self.execute_insert_data(data),
35            UpdateOperation::DeleteData { data } => self.execute_delete_data(data),
36            UpdateOperation::DeleteWhere { pattern } => self.execute_delete_where(pattern),
37            UpdateOperation::Modify {
38                delete,
39                insert,
40                where_clause,
41                using: _,
42            } => self.execute_modify(delete, insert, where_clause),
43            UpdateOperation::Load {
44                source,
45                destination,
46                silent,
47            } => self.execute_load(source, destination, *silent),
48            UpdateOperation::Clear { graph, silent } => self.execute_clear(graph, *silent),
49            UpdateOperation::Create { graph, silent } => self.execute_create(graph, *silent),
50            UpdateOperation::Drop { graph, silent } => self.execute_drop(graph, *silent),
51            UpdateOperation::Copy {
52                source,
53                destination,
54                silent,
55            } => self.execute_copy(source, destination, *silent),
56            UpdateOperation::Move {
57                source,
58                destination,
59                silent,
60            } => self.execute_move(source, destination, *silent),
61            UpdateOperation::Add {
62                source,
63                destination,
64                silent,
65            } => self.execute_add(source, destination, *silent),
66        }
67    }
68
69    /// Execute INSERT DATA operation
70    fn execute_insert_data(&self, data: &[Quad]) -> Result<()> {
71        for quad in data {
72            self.store.insert_quad(quad.clone())?;
73        }
74        Ok(())
75    }
76
77    /// Execute DELETE DATA operation
78    fn execute_delete_data(&self, data: &[Quad]) -> Result<()> {
79        for quad in data {
80            self.store.remove_quad(quad)?;
81        }
82        Ok(())
83    }
84
85    /// Execute DELETE WHERE operation
86    fn execute_delete_where(&self, patterns: &[QuadPattern]) -> Result<()> {
87        // Find all quads matching the pattern and delete them
88        for pattern in patterns {
89            let matching_quads = self.find_matching_quads(pattern)?;
90            for quad in matching_quads {
91                self.store.remove_quad(&quad)?;
92            }
93        }
94        Ok(())
95    }
96
97    /// Execute INSERT/DELETE WHERE operation
98    fn execute_modify(
99        &self,
100        delete_patterns: &Option<Vec<QuadPattern>>,
101        insert_patterns: &Option<Vec<QuadPattern>>,
102        where_clause: &GraphPattern,
103    ) -> Result<()> {
104        // First, execute the WHERE clause to get variable bindings
105        let solutions = self.evaluate_graph_pattern(where_clause)?;
106
107        // For each solution, apply the delete and insert patterns
108        for solution in solutions {
109            // Execute delete patterns first
110            if let Some(delete_patterns) = delete_patterns {
111                for pattern in delete_patterns {
112                    if let Some(quad) = self.instantiate_quad_pattern(pattern, &solution)? {
113                        self.store.remove_quad(&quad)?;
114                    }
115                }
116            }
117
118            // Then execute insert patterns
119            if let Some(insert_patterns) = insert_patterns {
120                for pattern in insert_patterns {
121                    if let Some(quad) = self.instantiate_quad_pattern(pattern, &solution)? {
122                        self.store.insert_quad(quad)?;
123                    }
124                }
125            }
126        }
127
128        Ok(())
129    }
130
131    /// Execute LOAD operation
132    fn execute_load(
133        &self,
134        source: &NamedNode,
135        _destination: &Option<NamedNode>,
136        _silent: bool,
137    ) -> Result<()> {
138        // For now, return an error as we don't have HTTP loading capability
139        // In a full implementation, this would fetch RDF from the source IRI
140        Err(OxirsError::Update(format!(
141            "LOAD operation not implemented for source: {source}"
142        )))
143    }
144
145    /// Execute CLEAR operation
146    fn execute_clear(&self, graph: &GraphTarget, _silent: bool) -> Result<()> {
147        match graph {
148            GraphTarget::Default => {
149                // Clear default graph
150                let default_graph = GraphName::DefaultGraph;
151                let quads = self
152                    .store
153                    .find_quads(None, None, None, Some(&default_graph))?;
154                for quad in quads {
155                    self.store.remove_quad(&quad)?;
156                }
157            }
158            GraphTarget::Named(graph_name) => {
159                // Clear named graph
160                let graph = GraphName::NamedNode(graph_name.clone());
161                let quads = self.store.find_quads(None, None, None, Some(&graph))?;
162                for quad in quads {
163                    self.store.remove_quad(&quad)?;
164                }
165            }
166            GraphTarget::All => {
167                // Clear all graphs
168                let quads = self.store.find_quads(None, None, None, None)?;
169                for quad in quads {
170                    self.store.remove_quad(&quad)?;
171                }
172            }
173        }
174        Ok(())
175    }
176
177    /// Execute CREATE operation
178    fn execute_create(&self, _graph: &NamedNode, _silent: bool) -> Result<()> {
179        // Graph creation is implicit in most RDF stores
180        // For now, this is a no-op
181        Ok(())
182    }
183
184    /// Execute DROP operation
185    fn execute_drop(&self, graph: &GraphTarget, _silent: bool) -> Result<()> {
186        // DROP is similar to CLEAR but also removes the graph container
187        // For now, we implement it the same as CLEAR
188        self.execute_clear(graph, _silent)
189    }
190
191    /// Execute COPY operation
192    fn execute_copy(
193        &self,
194        source: &GraphTarget,
195        destination: &GraphTarget,
196        _silent: bool,
197    ) -> Result<()> {
198        // First clear destination, then copy from source
199        self.execute_clear(destination, true)?;
200
201        let source_quads = self.get_quads_from_target(source)?;
202        for quad in source_quads {
203            let dest_quad = self.move_quad_to_target(&quad, destination)?;
204            self.store.insert_quad(dest_quad)?;
205        }
206        Ok(())
207    }
208
209    /// Execute MOVE operation
210    fn execute_move(
211        &self,
212        source: &GraphTarget,
213        destination: &GraphTarget,
214        _silent: bool,
215    ) -> Result<()> {
216        // MOVE = COPY + DROP source
217        self.execute_copy(source, destination, true)?;
218        self.execute_drop(source, true)?;
219        Ok(())
220    }
221
222    /// Execute ADD operation
223    fn execute_add(
224        &self,
225        source: &GraphTarget,
226        destination: &GraphTarget,
227        _silent: bool,
228    ) -> Result<()> {
229        // ADD is like COPY but doesn't clear destination first
230        let source_quads = self.get_quads_from_target(source)?;
231        for quad in source_quads {
232            let dest_quad = self.move_quad_to_target(&quad, destination)?;
233            self.store.insert_quad(dest_quad)?;
234        }
235        Ok(())
236    }
237
238    /// Find all quads matching a quad pattern
239    fn find_matching_quads(&self, pattern: &QuadPattern) -> Result<Vec<Quad>> {
240        // Convert pattern to query parameters and find matching quads
241        let subject = self.term_pattern_to_subject(&pattern.subject)?;
242        let predicate = self.term_pattern_to_predicate(&pattern.predicate)?;
243        let object = self.term_pattern_to_object(&pattern.object)?;
244        let graph = pattern
245            .graph
246            .as_ref()
247            .map(|g| self.term_pattern_to_graph_name(g))
248            .transpose()?;
249
250        self.store.find_quads(
251            subject.as_ref(),
252            predicate.as_ref(),
253            object.as_ref(),
254            graph.as_ref(),
255        )
256    }
257
258    /// Convert TermPattern to Subject (only if concrete)
259    fn term_pattern_to_subject(
260        &self,
261        pattern: &TermPattern,
262    ) -> Result<Option<crate::model::Subject>> {
263        match pattern {
264            TermPattern::NamedNode(n) => Ok(Some(crate::model::Subject::NamedNode(n.clone()))),
265            TermPattern::BlankNode(b) => Ok(Some(crate::model::Subject::BlankNode(b.clone()))),
266            TermPattern::Variable(_) => Ok(None), // Variables match anything
267            TermPattern::Literal(_) => Err(OxirsError::Update(
268                "Subject cannot be a literal".to_string(),
269            )),
270        }
271    }
272
273    /// Convert TermPattern to Predicate (only if concrete)
274    fn term_pattern_to_predicate(
275        &self,
276        pattern: &TermPattern,
277    ) -> Result<Option<crate::model::Predicate>> {
278        match pattern {
279            TermPattern::NamedNode(n) => Ok(Some(crate::model::Predicate::NamedNode(n.clone()))),
280            TermPattern::Variable(_) => Ok(None), // Variables match anything
281            TermPattern::BlankNode(_) | TermPattern::Literal(_) => Err(OxirsError::Update(
282                "Predicate must be a named node".to_string(),
283            )),
284        }
285    }
286
287    /// Convert TermPattern to Object (only if concrete)
288    fn term_pattern_to_object(
289        &self,
290        pattern: &TermPattern,
291    ) -> Result<Option<crate::model::Object>> {
292        match pattern {
293            TermPattern::NamedNode(n) => Ok(Some(crate::model::Object::NamedNode(n.clone()))),
294            TermPattern::BlankNode(b) => Ok(Some(crate::model::Object::BlankNode(b.clone()))),
295            TermPattern::Literal(l) => Ok(Some(crate::model::Object::Literal(l.clone()))),
296            TermPattern::Variable(_) => Ok(None), // Variables match anything
297        }
298    }
299
300    /// Convert TermPattern to GraphName (only if concrete)
301    fn term_pattern_to_graph_name(&self, pattern: &TermPattern) -> Result<GraphName> {
302        match pattern {
303            TermPattern::NamedNode(n) => Ok(GraphName::NamedNode(n.clone())),
304            TermPattern::Variable(_) => Ok(GraphName::DefaultGraph), // Default for variables
305            TermPattern::BlankNode(_) | TermPattern::Literal(_) => Err(OxirsError::Update(
306                "Graph name must be a named node".to_string(),
307            )),
308        }
309    }
310
311    /// Evaluate a graph pattern to get variable bindings
312    fn evaluate_graph_pattern(
313        &self,
314        pattern: &GraphPattern,
315    ) -> Result<Vec<HashMap<String, crate::model::Term>>> {
316        use crate::query::{QueryEngine, QueryResult};
317
318        // Create a temporary SELECT query to evaluate the pattern
319        let query_engine = QueryEngine::new();
320
321        // Convert the graph pattern to a SPARQL query string
322        let sparql_query = self.graph_pattern_to_sparql(pattern)?;
323
324        // Execute the query
325        match query_engine.query(&sparql_query, self.store)? {
326            QueryResult::Select {
327                variables: _,
328                bindings,
329            } => {
330                // Convert the bindings to the expected format
331                let mut solutions = Vec::new();
332                for binding in bindings {
333                    let mut solution = HashMap::new();
334                    for (var_name, term) in binding {
335                        solution.insert(var_name, term);
336                    }
337                    solutions.push(solution);
338                }
339                Ok(solutions)
340            }
341            _ => Err(OxirsError::Update(
342                "Expected SELECT query result for WHERE clause evaluation".to_string(),
343            )),
344        }
345    }
346
347    /// Instantiate a quad pattern with variable bindings
348    fn instantiate_quad_pattern(
349        &self,
350        pattern: &QuadPattern,
351        solution: &HashMap<String, crate::model::Term>,
352    ) -> Result<Option<Quad>> {
353        use crate::model::*;
354
355        // Instantiate subject
356        let subject = match &pattern.subject {
357            TermPattern::Variable(var) => {
358                if let Some(term) = solution.get(var.name()) {
359                    match term {
360                        Term::NamedNode(n) => Subject::NamedNode(n.clone()),
361                        Term::BlankNode(b) => Subject::BlankNode(b.clone()),
362                        _ => return Ok(None), // Invalid subject type
363                    }
364                } else {
365                    return Ok(None); // Unbound variable
366                }
367            }
368            TermPattern::NamedNode(n) => Subject::NamedNode(n.clone()),
369            TermPattern::BlankNode(b) => Subject::BlankNode(b.clone()),
370            TermPattern::Literal(_) => return Ok(None), // Subject cannot be literal
371        };
372
373        // Instantiate predicate
374        let predicate = match &pattern.predicate {
375            TermPattern::Variable(var) => {
376                if let Some(Term::NamedNode(n)) = solution.get(var.name()) {
377                    Predicate::NamedNode(n.clone())
378                } else {
379                    return Ok(None); // Unbound variable or invalid predicate type
380                }
381            }
382            TermPattern::NamedNode(n) => Predicate::NamedNode(n.clone()),
383            _ => return Ok(None), // Predicate must be named node
384        };
385
386        // Instantiate object
387        let object = match &pattern.object {
388            TermPattern::Variable(var) => {
389                if let Some(term) = solution.get(var.name()) {
390                    match term {
391                        Term::NamedNode(n) => Object::NamedNode(n.clone()),
392                        Term::BlankNode(b) => Object::BlankNode(b.clone()),
393                        Term::Literal(l) => Object::Literal(l.clone()),
394                        _ => return Ok(None), // Invalid object type
395                    }
396                } else {
397                    return Ok(None); // Unbound variable
398                }
399            }
400            TermPattern::NamedNode(n) => Object::NamedNode(n.clone()),
401            TermPattern::BlankNode(b) => Object::BlankNode(b.clone()),
402            TermPattern::Literal(l) => Object::Literal(l.clone()),
403        };
404
405        // Instantiate graph name
406        let graph_name = match &pattern.graph {
407            Some(graph_pattern) => match graph_pattern {
408                TermPattern::Variable(var) => {
409                    if let Some(Term::NamedNode(n)) = solution.get(var.name()) {
410                        GraphName::NamedNode(n.clone())
411                    } else {
412                        return Ok(None); // Unbound variable or invalid graph name
413                    }
414                }
415                TermPattern::NamedNode(n) => GraphName::NamedNode(n.clone()),
416                _ => return Ok(None), // Graph name must be named node
417            },
418            None => GraphName::DefaultGraph,
419        };
420
421        Ok(Some(Quad::new(subject, predicate, object, graph_name)))
422    }
423
424    /// Get all quads from a graph target
425    fn get_quads_from_target(&self, target: &GraphTarget) -> Result<Vec<Quad>> {
426        match target {
427            GraphTarget::Default => {
428                let graph = GraphName::DefaultGraph;
429                self.store.find_quads(None, None, None, Some(&graph))
430            }
431            GraphTarget::Named(graph_name) => {
432                let graph = GraphName::NamedNode(graph_name.clone());
433                self.store.find_quads(None, None, None, Some(&graph))
434            }
435            GraphTarget::All => self.store.find_quads(None, None, None, None),
436        }
437    }
438
439    /// Move a quad to a different graph target
440    fn move_quad_to_target(&self, quad: &Quad, target: &GraphTarget) -> Result<Quad> {
441        match target {
442            GraphTarget::Default => Ok(Quad::new(
443                quad.subject().clone(),
444                quad.predicate().clone(),
445                quad.object().clone(),
446                GraphName::DefaultGraph,
447            )),
448            GraphTarget::Named(graph_name) => Ok(Quad::new(
449                quad.subject().clone(),
450                quad.predicate().clone(),
451                quad.object().clone(),
452                GraphName::NamedNode(graph_name.clone()),
453            )),
454            GraphTarget::All => {
455                // For "ALL", we keep the original graph
456                Ok(quad.clone())
457            }
458        }
459    }
460
461    /// Convert a graph pattern to a SPARQL query string
462    fn graph_pattern_to_sparql(&self, pattern: &GraphPattern) -> Result<String> {
463        use crate::query::algebra::*;
464
465        match pattern {
466            GraphPattern::Bgp(triple_patterns) => {
467                let mut sparql = String::from("SELECT * WHERE { ");
468                for (i, triple_pattern) in triple_patterns.iter().enumerate() {
469                    if i > 0 {
470                        sparql.push_str(" . ");
471                    }
472                    sparql.push_str(&self.triple_pattern_to_sparql(triple_pattern)?);
473                }
474                sparql.push_str(" }");
475                Ok(sparql)
476            }
477            GraphPattern::Join(left, right) => {
478                let left_sparql = self.graph_pattern_to_sparql(left)?;
479                let right_sparql = self.graph_pattern_to_sparql(right)?;
480
481                // Extract WHERE clause content from both patterns
482                let left_where = self.extract_where_clause(&left_sparql)?;
483                let right_where = self.extract_where_clause(&right_sparql)?;
484
485                Ok(format!("SELECT * WHERE {{ {left_where} . {right_where} }}"))
486            }
487            GraphPattern::Filter { expr, inner } => {
488                let inner_sparql = self.graph_pattern_to_sparql(inner)?;
489                let inner_where = self.extract_where_clause(&inner_sparql)?;
490                let filter_expr = self.expression_to_sparql(expr)?;
491
492                Ok(format!(
493                    "SELECT * WHERE {{ {inner_where} FILTER ({filter_expr}) }}"
494                ))
495            }
496            GraphPattern::Union(left, right) => {
497                let left_sparql = self.graph_pattern_to_sparql(left)?;
498                let right_sparql = self.graph_pattern_to_sparql(right)?;
499
500                let left_where = self.extract_where_clause(&left_sparql)?;
501                let right_where = self.extract_where_clause(&right_sparql)?;
502
503                Ok(format!(
504                    "SELECT * WHERE {{ {{ {left_where} }} UNION {{ {right_where} }} }}"
505                ))
506            }
507            _ => Err(OxirsError::Update(format!(
508                "Graph pattern type not yet supported in SPARQL conversion: {pattern:?}"
509            ))),
510        }
511    }
512
513    /// Convert a triple pattern to SPARQL syntax
514    fn triple_pattern_to_sparql(&self, pattern: &AlgebraTriplePattern) -> Result<String> {
515        let subject = self.term_pattern_to_sparql(&pattern.subject)?;
516        let predicate = self.term_pattern_to_sparql(&pattern.predicate)?;
517        let object = self.term_pattern_to_sparql(&pattern.object)?;
518
519        Ok(format!("{subject} {predicate} {object}"))
520    }
521
522    /// Convert a term pattern to SPARQL syntax
523    fn term_pattern_to_sparql(&self, pattern: &TermPattern) -> Result<String> {
524        match pattern {
525            TermPattern::Variable(var) => Ok(format!("?{}", var.name())),
526            TermPattern::NamedNode(node) => Ok(format!("<{}>", node.as_str())),
527            TermPattern::BlankNode(blank) => Ok(format!("_:{}", blank.as_str())),
528            TermPattern::Literal(literal) => {
529                if let Some(lang) = literal.language() {
530                    Ok(format!("\"{}\"@{}", literal.value(), lang))
531                } else if literal.datatype() != xsd::STRING.as_ref() {
532                    Ok(format!("\"{}\"^^<{}>", literal.value(), literal.datatype()))
533                } else {
534                    Ok(format!("\"{}\"", literal.value()))
535                }
536            }
537        }
538    }
539
540    /// Convert an expression to SPARQL syntax
541    #[allow(clippy::only_used_in_recursion)]
542    fn expression_to_sparql(&self, expr: &Expression) -> Result<String> {
543        match expr {
544            Expression::Variable(var) => Ok(format!("?{}", var.name())),
545            Expression::Term(term) => match term {
546                crate::model::Term::NamedNode(n) => Ok(format!("<{}>", n.as_str())),
547                crate::model::Term::BlankNode(b) => Ok(format!("_:{}", b.as_str())),
548                crate::model::Term::Literal(l) => {
549                    if let Some(lang) = l.language() {
550                        Ok(format!("\"{}\"@{}", l.value(), lang))
551                    } else if l.datatype() != xsd::STRING.as_ref() {
552                        Ok(format!("\"{}\"^^<{}>", l.value(), l.datatype()))
553                    } else {
554                        Ok(format!("\"{}\"", l.value()))
555                    }
556                }
557                _ => Err(OxirsError::Update(
558                    "Unsupported term type in expression".to_string(),
559                )),
560            },
561            Expression::Equal(left, right) => {
562                let left_sparql = self.expression_to_sparql(left)?;
563                let right_sparql = self.expression_to_sparql(right)?;
564                Ok(format!("({left_sparql} = {right_sparql})"))
565            }
566            Expression::And(left, right) => {
567                let left_sparql = self.expression_to_sparql(left)?;
568                let right_sparql = self.expression_to_sparql(right)?;
569                Ok(format!("({left_sparql} && {right_sparql})"))
570            }
571            Expression::Or(left, right) => {
572                let left_sparql = self.expression_to_sparql(left)?;
573                let right_sparql = self.expression_to_sparql(right)?;
574                Ok(format!("({left_sparql} || {right_sparql})"))
575            }
576            Expression::Not(inner) => {
577                let inner_sparql = self.expression_to_sparql(inner)?;
578                Ok(format!("(!{inner_sparql})"))
579            }
580            _ => Err(OxirsError::Update(format!(
581                "Expression type not yet supported in SPARQL conversion: {expr:?}"
582            ))),
583        }
584    }
585
586    /// Extract WHERE clause content from a SPARQL query
587    fn extract_where_clause(&self, sparql: &str) -> Result<String> {
588        if let Some(start) = sparql.find("WHERE {") {
589            let where_start = start + 7; // Length of "WHERE {"
590            if let Some(end) = sparql.rfind('}') {
591                let where_content = &sparql[where_start..end].trim();
592                Ok(where_content.to_string())
593            } else {
594                Err(OxirsError::Update(
595                    "Malformed SPARQL query: missing closing brace".to_string(),
596                ))
597            }
598        } else {
599            Err(OxirsError::Update(
600                "Malformed SPARQL query: missing WHERE clause".to_string(),
601            ))
602        }
603    }
604}
605
606/// SPARQL UPDATE parser (simplified)
607#[derive(Default)]
608pub struct UpdateParser;
609
610impl UpdateParser {
611    /// Create a new update parser
612    pub fn new() -> Self {
613        Self
614    }
615
616    /// Parse a SPARQL UPDATE string into an Update struct
617    pub fn parse(&self, update_str: &str) -> Result<Update> {
618        // This is a simplified parser that handles common UPDATE operations
619        // A full implementation would need a complete SPARQL UPDATE grammar parser
620
621        let trimmed = update_str.trim();
622
623        // Extract prefixes first
624        let (prefixes, remaining) = self.extract_prefixes(trimmed)?;
625
626        // Determine operation type
627        if remaining.contains("INSERT DATA") {
628            self.parse_insert_data(&remaining, prefixes)
629        } else if remaining.contains("DELETE DATA") {
630            self.parse_delete_data(&remaining, prefixes)
631        } else if self.is_delete_where_shorthand(&remaining) {
632            self.parse_delete_where(&remaining, prefixes)
633        } else if remaining.contains("DELETE") && remaining.contains("WHERE") {
634            self.parse_delete_modify(&remaining, prefixes)
635        } else if remaining.contains("INSERT") && remaining.contains("WHERE") {
636            self.parse_insert_where(&remaining, prefixes)
637        } else if remaining.contains("CLEAR") {
638            self.parse_clear(&remaining, prefixes)
639        } else {
640            Err(OxirsError::Parse(format!(
641                "Unsupported UPDATE operation: {}",
642                remaining
643            )))
644        }
645    }
646
647    /// Check if this is DELETE WHERE shorthand (no braces between DELETE and WHERE)
648    fn is_delete_where_shorthand(&self, update_str: &str) -> bool {
649        // DELETE WHERE means DELETE followed directly by WHERE with only whitespace between
650        // DELETE { ... } WHERE { ... } should return false
651        if let Some(delete_pos) = update_str.find("DELETE") {
652            if let Some(where_pos) = update_str.find("WHERE") {
653                let between = &update_str[delete_pos + 6..where_pos];
654                // If there's an opening brace between DELETE and WHERE, it's not shorthand
655                return !between.contains('{');
656            }
657        }
658        false
659    }
660
661    /// Extract PREFIX declarations from UPDATE string
662    fn extract_prefixes(&self, update_str: &str) -> Result<(HashMap<String, NamedNode>, String)> {
663        let mut prefixes = HashMap::new();
664        let mut remaining = update_str.to_string();
665
666        // Handle PREFIX declarations that may be inline or multi-line
667        loop {
668            let trimmed = remaining.trim();
669
670            if let Some(prefix_start) = trimmed.find("PREFIX") {
671                // Check if this is at the start or after whitespace (not in the middle of an IRI)
672                if prefix_start == 0 || trimmed[..prefix_start].chars().all(|c| c.is_whitespace()) {
673                    // Extract PREFIX declaration: PREFIX prefix: <iri>
674                    let after_prefix = &trimmed[prefix_start + 6..];
675
676                    if let Some(colon_pos) = after_prefix.find(':') {
677                        if let Some(iri_start) = after_prefix.find('<') {
678                            if let Some(iri_end) = after_prefix.find('>') {
679                                let prefix = after_prefix[..colon_pos].trim().to_string();
680                                let iri_str = &after_prefix[iri_start + 1..iri_end];
681                                let iri_node = NamedNode::new(iri_str).map_err(|e| {
682                                    OxirsError::Parse(format!("Invalid prefix IRI: {e}"))
683                                })?;
684                                prefixes.insert(prefix, iri_node);
685
686                                // Remove this PREFIX declaration from remaining
687                                remaining = after_prefix[iri_end + 1..].to_string();
688                                continue;
689                            }
690                        }
691                    }
692                }
693            }
694
695            // No more PREFIX declarations found
696            break;
697        }
698
699        Ok((prefixes, remaining.trim().to_string()))
700    }
701
702    /// Parse INSERT DATA operation
703    fn parse_insert_data(
704        &self,
705        update_str: &str,
706        prefixes: HashMap<String, NamedNode>,
707    ) -> Result<Update> {
708        use crate::query::algebra::UpdateOperation;
709
710        // Extract the data block from "INSERT DATA { ... }"
711        let data_start = update_str.find('{');
712        let data_end = update_str.rfind('}');
713
714        if let (Some(start), Some(end)) = (data_start, data_end) {
715            let data_block = update_str[start + 1..end].trim();
716
717            // Parse the quads from the data block
718            let quads = self.parse_quad_data(data_block, &prefixes)?;
719
720            Ok(Update {
721                base: None,
722                prefixes,
723                operations: vec![UpdateOperation::InsertData { data: quads }],
724            })
725        } else {
726            Err(OxirsError::Parse(
727                "Malformed INSERT DATA: missing data block".to_string(),
728            ))
729        }
730    }
731
732    /// Parse DELETE DATA operation
733    fn parse_delete_data(
734        &self,
735        update_str: &str,
736        prefixes: HashMap<String, NamedNode>,
737    ) -> Result<Update> {
738        use crate::query::algebra::UpdateOperation;
739
740        // Extract the data block from "DELETE DATA { ... }"
741        let data_start = update_str.find('{');
742        let data_end = update_str.rfind('}');
743
744        if let (Some(start), Some(end)) = (data_start, data_end) {
745            let data_block = update_str[start + 1..end].trim();
746
747            // Parse the quads from the data block
748            let quads = self.parse_quad_data(data_block, &prefixes)?;
749
750            Ok(Update {
751                base: None,
752                prefixes,
753                operations: vec![UpdateOperation::DeleteData { data: quads }],
754            })
755        } else {
756            Err(OxirsError::Parse(
757                "Malformed DELETE DATA: missing data block".to_string(),
758            ))
759        }
760    }
761
762    /// Parse quad data from a data block using Turtle syntax
763    fn parse_quad_data(
764        &self,
765        data_block: &str,
766        prefixes: &HashMap<String, NamedNode>,
767    ) -> Result<Vec<Quad>> {
768        use crate::format::format::RdfFormat;
769        use crate::format::RdfParser;
770        use std::io::Cursor;
771
772        // Build a complete Turtle document with prefixes
773        let mut turtle_doc = String::new();
774        for (prefix, iri) in prefixes {
775            turtle_doc.push_str(&format!("@prefix {}: <{}> .\n", prefix, iri.as_str()));
776        }
777        turtle_doc.push_str("\n");
778        turtle_doc.push_str(data_block);
779
780        // Parse using Turtle parser
781        let parser = RdfParser::new(RdfFormat::Turtle);
782        let turtle_bytes = turtle_doc.into_bytes();
783        let cursor = Cursor::new(turtle_bytes);
784
785        let quads: Vec<Quad> = parser
786            .for_reader(cursor)
787            .collect::<std::result::Result<Vec<_>, _>>()
788            .map_err(|e| OxirsError::Parse(format!("Failed to parse UPDATE data: {}", e)))?;
789
790        Ok(quads)
791    }
792
793    /// Parse DELETE { ... } WHERE { ... } operation
794    fn parse_delete_modify(
795        &self,
796        update_str: &str,
797        prefixes: HashMap<String, NamedNode>,
798    ) -> Result<Update> {
799        use crate::query::algebra::UpdateOperation;
800
801        // Parse "DELETE { template } WHERE { pattern }"
802        let delete_pos = update_str.find("DELETE");
803        let where_pos = update_str.find("WHERE");
804
805        if delete_pos.is_none() || where_pos.is_none() {
806            return Err(OxirsError::Parse(
807                "Malformed DELETE/WHERE: missing DELETE or WHERE keyword".to_string(),
808            ));
809        }
810
811        let delete_start = update_str[delete_pos.unwrap()..].find('{');
812        let delete_end = update_str[delete_pos.unwrap()..].find('}');
813
814        if delete_start.is_none() || delete_end.is_none() {
815            return Err(OxirsError::Parse(
816                "Malformed DELETE/WHERE: missing template block".to_string(),
817            ));
818        }
819
820        let template_start = delete_pos.unwrap() + delete_start.unwrap() + 1;
821        let template_end = delete_pos.unwrap() + delete_end.unwrap();
822        let template_block = update_str[template_start..template_end].trim();
823
824        // Parse delete template as template patterns (can contain variables)
825        let delete_patterns = self.parse_template_patterns(template_block, &prefixes)?;
826
827        // Extract WHERE clause
828        let where_start = update_str[where_pos.unwrap()..].find('{');
829        let where_end = update_str[where_pos.unwrap()..].rfind('}');
830
831        if where_start.is_none() || where_end.is_none() {
832            return Err(OxirsError::Parse(
833                "Malformed DELETE/WHERE: missing WHERE pattern block".to_string(),
834            ));
835        }
836
837        let where_pattern_start = where_pos.unwrap() + where_start.unwrap() + 1;
838        let where_pattern_end = where_pos.unwrap() + where_end.unwrap();
839        let where_block = update_str[where_pattern_start..where_pattern_end].trim();
840
841        // Parse WHERE clause as graph pattern
842        let where_pattern = self.parse_where_pattern(where_block, &prefixes)?;
843
844        Ok(Update {
845            base: None,
846            prefixes,
847            operations: vec![UpdateOperation::Modify {
848                delete: Some(delete_patterns),
849                insert: None,
850                where_clause: Box::new(where_pattern),
851                using: crate::query::algebra::Dataset {
852                    default: vec![],
853                    named: vec![],
854                },
855            }],
856        })
857    }
858
859    /// Parse DELETE WHERE operation (shorthand)
860    fn parse_delete_where(
861        &self,
862        update_str: &str,
863        prefixes: HashMap<String, NamedNode>,
864    ) -> Result<Update> {
865        use crate::query::algebra::UpdateOperation;
866
867        // Extract the pattern from "DELETE WHERE { ... }"
868        let pattern_start = update_str.find('{');
869        let pattern_end = update_str.rfind('}');
870
871        if let (Some(start), Some(end)) = (pattern_start, pattern_end) {
872            let pattern_block = update_str[start + 1..end].trim();
873
874            // Parse patterns as quad patterns
875            let patterns = self.parse_quad_patterns(pattern_block, &prefixes)?;
876
877            Ok(Update {
878                base: None,
879                prefixes,
880                operations: vec![UpdateOperation::DeleteWhere { pattern: patterns }],
881            })
882        } else {
883            Err(OxirsError::Parse(
884                "Malformed DELETE WHERE: missing pattern block".to_string(),
885            ))
886        }
887    }
888
889    /// Parse INSERT WHERE operation
890    fn parse_insert_where(
891        &self,
892        update_str: &str,
893        prefixes: HashMap<String, NamedNode>,
894    ) -> Result<Update> {
895        use crate::query::algebra::UpdateOperation;
896
897        // Parse "INSERT { template } WHERE { pattern }"
898        let insert_pos = update_str.find("INSERT");
899        let where_pos = update_str.find("WHERE");
900
901        if insert_pos.is_none() || where_pos.is_none() {
902            return Err(OxirsError::Parse(
903                "Malformed INSERT WHERE: missing INSERT or WHERE keyword".to_string(),
904            ));
905        }
906
907        let insert_start = update_str[insert_pos.unwrap()..].find('{');
908        let insert_end = update_str[insert_pos.unwrap()..].find('}');
909
910        if insert_start.is_none() || insert_end.is_none() {
911            return Err(OxirsError::Parse(
912                "Malformed INSERT WHERE: missing template block".to_string(),
913            ));
914        }
915
916        let template_start = insert_pos.unwrap() + insert_start.unwrap() + 1;
917        let template_end = insert_pos.unwrap() + insert_end.unwrap();
918        let template_block = update_str[template_start..template_end].trim();
919
920        // Parse insert template as template patterns (can contain variables)
921        let insert_patterns = self.parse_template_patterns(template_block, &prefixes)?;
922
923        // Extract WHERE clause
924        let where_start = update_str[where_pos.unwrap()..].find('{');
925        let where_end = update_str[where_pos.unwrap()..].rfind('}');
926
927        if where_start.is_none() || where_end.is_none() {
928            return Err(OxirsError::Parse(
929                "Malformed INSERT WHERE: missing WHERE pattern block".to_string(),
930            ));
931        }
932
933        let where_pattern_start = where_pos.unwrap() + where_start.unwrap() + 1;
934        let where_pattern_end = where_pos.unwrap() + where_end.unwrap();
935        let where_block = update_str[where_pattern_start..where_pattern_end].trim();
936
937        // Parse WHERE clause as graph pattern
938        let where_pattern = self.parse_where_pattern(where_block, &prefixes)?;
939
940        Ok(Update {
941            base: None,
942            prefixes,
943            operations: vec![UpdateOperation::Modify {
944                delete: None,
945                insert: Some(insert_patterns),
946                where_clause: Box::new(where_pattern),
947                using: crate::query::algebra::Dataset {
948                    default: vec![],
949                    named: vec![],
950                },
951            }],
952        })
953    }
954
955    /// Parse quad patterns from a pattern block (for concrete data without variables)
956    fn parse_quad_patterns(
957        &self,
958        pattern_block: &str,
959        prefixes: &HashMap<String, NamedNode>,
960    ) -> Result<Vec<crate::query::algebra::QuadPattern>> {
961        use crate::format::format::RdfFormat;
962        use crate::format::RdfParser;
963        use crate::query::algebra::QuadPattern;
964        use std::io::Cursor;
965
966        // Build a complete Turtle document with prefixes to parse as triples
967        let mut turtle_doc = String::new();
968        for (prefix, iri) in prefixes {
969            turtle_doc.push_str(&format!("@prefix {}: <{}> .\n", prefix, iri.as_str()));
970        }
971        turtle_doc.push_str("\n");
972        turtle_doc.push_str(pattern_block);
973
974        // Parse using Turtle parser to get concrete quads
975        let parser = RdfParser::new(RdfFormat::Turtle);
976        let turtle_bytes = turtle_doc.into_bytes();
977        let cursor = Cursor::new(turtle_bytes);
978
979        let quads: Vec<Quad> = parser
980            .for_reader(cursor)
981            .collect::<std::result::Result<Vec<_>, _>>()
982            .map_err(|e| OxirsError::Parse(format!("Failed to parse pattern: {}", e)))?;
983
984        // Convert Quads to QuadPatterns (for DELETE WHERE, these are concrete patterns)
985        let quad_patterns: Vec<QuadPattern> = quads
986            .into_iter()
987            .map(|quad| QuadPattern {
988                subject: self.subject_to_term_pattern(&quad.subject()),
989                predicate: self.predicate_to_term_pattern(&quad.predicate()),
990                object: self.object_to_term_pattern(&quad.object()),
991                graph: Some(self.graph_to_term_pattern(&quad.graph_name())),
992            })
993            .collect();
994
995        Ok(quad_patterns)
996    }
997
998    /// Parse template patterns that can contain variables (for DELETE/INSERT templates)
999    fn parse_template_patterns(
1000        &self,
1001        template_block: &str,
1002        prefixes: &HashMap<String, NamedNode>,
1003    ) -> Result<Vec<crate::query::algebra::QuadPattern>> {
1004        use crate::query::algebra::QuadPattern;
1005
1006        // Split by periods to get individual triple patterns
1007        let pattern_lines: Vec<&str> = template_block
1008            .split('.')
1009            .map(|s| s.trim())
1010            .filter(|s| !s.is_empty() && *s != "}")
1011            .collect();
1012
1013        let mut quad_patterns = Vec::new();
1014
1015        for line in pattern_lines {
1016            // Parse triple pattern: ?s ?p ?o or prefix:subject ?p ?o
1017            let parts: Vec<&str> = line.split_whitespace().collect();
1018            if parts.len() >= 3 {
1019                let subject = self.parse_term_pattern_with_prefix(parts[0], prefixes)?;
1020                let predicate = self.parse_term_pattern_with_prefix(parts[1], prefixes)?;
1021                let object = self.parse_term_pattern_with_prefix(parts[2], prefixes)?;
1022
1023                quad_patterns.push(QuadPattern {
1024                    subject,
1025                    predicate,
1026                    object,
1027                    graph: None, // Default graph
1028                });
1029            }
1030        }
1031
1032        Ok(quad_patterns)
1033    }
1034
1035    /// Parse WHERE clause pattern block
1036    fn parse_where_pattern(
1037        &self,
1038        pattern_block: &str,
1039        prefixes: &HashMap<String, NamedNode>,
1040    ) -> Result<crate::query::algebra::GraphPattern> {
1041        use crate::query::algebra::{AlgebraTriplePattern, GraphPattern};
1042
1043        // For simplicity, parse as a basic graph pattern (BGP)
1044        // A full implementation would use a complete SPARQL parser
1045
1046        // Split pattern block by periods to get individual triple patterns
1047        let pattern_lines: Vec<&str> = pattern_block
1048            .split('.')
1049            .map(|s| s.trim())
1050            .filter(|s| !s.is_empty() && !s.starts_with("FILTER"))
1051            .collect();
1052
1053        let mut triple_patterns = Vec::new();
1054
1055        for line in pattern_lines {
1056            // Simple triple pattern parsing: ?s ?p ?o or prefix:subject ?p ?o
1057            let parts: Vec<&str> = line.split_whitespace().collect();
1058            if parts.len() >= 3 {
1059                let subject = self.parse_term_pattern_with_prefix(parts[0], prefixes)?;
1060                let predicate = self.parse_term_pattern_with_prefix(parts[1], prefixes)?;
1061                let object = self.parse_term_pattern_with_prefix(parts[2], prefixes)?;
1062
1063                triple_patterns.push(AlgebraTriplePattern {
1064                    subject,
1065                    predicate,
1066                    object,
1067                });
1068            }
1069        }
1070
1071        Ok(GraphPattern::Bgp(triple_patterns))
1072    }
1073
1074    /// Parse a term pattern from string
1075    #[allow(dead_code)]
1076    fn parse_term_pattern(&self, term_str: &str) -> Result<TermPattern> {
1077        let trimmed = term_str.trim();
1078
1079        if trimmed.starts_with('?') {
1080            // Variable
1081            let var_name = &trimmed[1..];
1082            let var = crate::model::Variable::new(var_name)
1083                .map_err(|e| OxirsError::Parse(format!("Invalid variable: {e}")))?;
1084            Ok(TermPattern::Variable(var))
1085        } else if trimmed.starts_with('<') && trimmed.ends_with('>') {
1086            // Named node (IRI)
1087            let iri = &trimmed[1..trimmed.len() - 1];
1088            let node =
1089                NamedNode::new(iri).map_err(|e| OxirsError::Parse(format!("Invalid IRI: {e}")))?;
1090            Ok(TermPattern::NamedNode(node))
1091        } else if trimmed.starts_with('"') {
1092            // Literal
1093            // Simple literal parsing - full implementation would handle language tags and datatypes
1094            let lit_value = trimmed.trim_matches('"');
1095            Ok(TermPattern::Literal(
1096                crate::model::Literal::new_simple_literal(lit_value),
1097            ))
1098        } else {
1099            Err(OxirsError::Parse(format!(
1100                "Cannot parse term pattern: {}",
1101                term_str
1102            )))
1103        }
1104    }
1105
1106    /// Parse a term pattern from string with prefix expansion
1107    fn parse_term_pattern_with_prefix(
1108        &self,
1109        term_str: &str,
1110        prefixes: &HashMap<String, NamedNode>,
1111    ) -> Result<TermPattern> {
1112        let trimmed = term_str.trim();
1113
1114        if trimmed.starts_with('?') {
1115            // Variable
1116            let var_name = &trimmed[1..];
1117            let var = crate::model::Variable::new(var_name)
1118                .map_err(|e| OxirsError::Parse(format!("Invalid variable: {e}")))?;
1119            Ok(TermPattern::Variable(var))
1120        } else if trimmed.starts_with('<') && trimmed.ends_with('>') {
1121            // Named node (IRI)
1122            let iri = &trimmed[1..trimmed.len() - 1];
1123            let node =
1124                NamedNode::new(iri).map_err(|e| OxirsError::Parse(format!("Invalid IRI: {e}")))?;
1125            Ok(TermPattern::NamedNode(node))
1126        } else if trimmed.starts_with('"') {
1127            // Literal
1128            // Simple literal parsing - full implementation would handle language tags and datatypes
1129            let lit_value = trimmed.trim_matches('"');
1130            Ok(TermPattern::Literal(
1131                crate::model::Literal::new_simple_literal(lit_value),
1132            ))
1133        } else if trimmed.contains(':') {
1134            // Prefixed name like foaf:name
1135            let parts: Vec<&str> = trimmed.splitn(2, ':').collect();
1136            if parts.len() == 2 {
1137                let prefix = parts[0];
1138                let local = parts[1];
1139
1140                if let Some(base_iri) = prefixes.get(prefix) {
1141                    // Expand prefix to full IRI
1142                    let full_iri = format!("{}{}", base_iri.as_str(), local);
1143                    let node = NamedNode::new(&full_iri)
1144                        .map_err(|e| OxirsError::Parse(format!("Invalid expanded IRI: {e}")))?;
1145                    Ok(TermPattern::NamedNode(node))
1146                } else {
1147                    Err(OxirsError::Parse(format!("Unknown prefix: {}", prefix)))
1148                }
1149            } else {
1150                Err(OxirsError::Parse(format!(
1151                    "Invalid prefixed name: {}",
1152                    term_str
1153                )))
1154            }
1155        } else {
1156            Err(OxirsError::Parse(format!(
1157                "Cannot parse term pattern: {}",
1158                term_str
1159            )))
1160        }
1161    }
1162
1163    /// Convert Subject to TermPattern
1164    fn subject_to_term_pattern(&self, subject: &crate::model::Subject) -> TermPattern {
1165        use crate::model::Subject;
1166        match subject {
1167            Subject::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1168            Subject::BlankNode(b) => TermPattern::BlankNode(b.clone()),
1169            Subject::Variable(v) => TermPattern::Variable(v.clone()),
1170            Subject::QuotedTriple(_) => {
1171                // RDF-star support - for now treat as variable
1172                TermPattern::Variable(
1173                    crate::model::Variable::new("quotedTriple")
1174                        .expect("quotedTriple is a valid variable name"),
1175                )
1176            }
1177        }
1178    }
1179
1180    /// Convert Predicate to TermPattern
1181    fn predicate_to_term_pattern(&self, predicate: &crate::model::Predicate) -> TermPattern {
1182        use crate::model::Predicate;
1183        match predicate {
1184            Predicate::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1185            Predicate::Variable(v) => TermPattern::Variable(v.clone()),
1186        }
1187    }
1188
1189    /// Convert Object to TermPattern
1190    fn object_to_term_pattern(&self, object: &crate::model::Object) -> TermPattern {
1191        use crate::model::Object;
1192        match object {
1193            Object::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1194            Object::BlankNode(b) => TermPattern::BlankNode(b.clone()),
1195            Object::Literal(l) => TermPattern::Literal(l.clone()),
1196            Object::Variable(v) => TermPattern::Variable(v.clone()),
1197            Object::QuotedTriple(_) => {
1198                // RDF-star support
1199                TermPattern::Variable(
1200                    crate::model::Variable::new("quotedTripleObj")
1201                        .expect("quotedTripleObj is a valid variable name"),
1202                )
1203            }
1204        }
1205    }
1206
1207    /// Convert GraphName to TermPattern
1208    fn graph_to_term_pattern(&self, graph: &GraphName) -> TermPattern {
1209        match graph {
1210            GraphName::NamedNode(n) => TermPattern::NamedNode(n.clone()),
1211            GraphName::BlankNode(b) => TermPattern::BlankNode(b.clone()),
1212            GraphName::Variable(v) => TermPattern::Variable(v.clone()),
1213            GraphName::DefaultGraph => {
1214                // Default graph represented as a special variable
1215                TermPattern::Variable(
1216                    crate::model::Variable::new("defaultGraph")
1217                        .expect("defaultGraph is a valid variable name"),
1218                )
1219            }
1220        }
1221    }
1222
1223    /// Parse CLEAR operation
1224    fn parse_clear(
1225        &self,
1226        update_str: &str,
1227        prefixes: HashMap<String, NamedNode>,
1228    ) -> Result<Update> {
1229        use crate::query::algebra::{GraphTarget, UpdateOperation};
1230
1231        let trimmed = update_str.trim();
1232        let silent = trimmed.contains("SILENT");
1233
1234        let graph_target = if trimmed.contains("DEFAULT") {
1235            GraphTarget::Default
1236        } else if trimmed.contains("ALL") {
1237            GraphTarget::All
1238        } else if let Some(graph_start) = trimmed.find("GRAPH") {
1239            // Extract the graph IRI
1240            let after_graph = &trimmed[graph_start + 5..].trim();
1241            if let Some(iri_start) = after_graph.find('<') {
1242                if let Some(iri_end) = after_graph.find('>') {
1243                    let iri_str = &after_graph[iri_start + 1..iri_end];
1244                    let graph_node = NamedNode::new(iri_str)
1245                        .map_err(|e| OxirsError::Parse(format!("Invalid graph IRI: {e}")))?;
1246                    GraphTarget::Named(graph_node)
1247                } else {
1248                    return Err(OxirsError::Parse(
1249                        "Malformed graph IRI in CLEAR".to_string(),
1250                    ));
1251                }
1252            } else {
1253                return Err(OxirsError::Parse("Missing graph IRI in CLEAR".to_string()));
1254            }
1255        } else {
1256            GraphTarget::Default // Default if no target specified
1257        };
1258
1259        Ok(Update {
1260            base: None,
1261            prefixes,
1262            operations: vec![UpdateOperation::Clear {
1263                graph: graph_target,
1264                silent,
1265            }],
1266        })
1267    }
1268}