Skip to main content

oxirs_arq/
update_protocol_executor.rs

1//! In-memory executor for SPARQL 1.1 Update operations.
2//!
3//! [`UpdateExecutor`] is a minimal store-independent dataset executor used by
4//! the standalone update protocol facade.  It maintains a *default graph*
5//! (a `Vec<Triple>`) and an arbitrary number of *named graphs* keyed by IRI
6//! string.  Pattern-based operations perform a simple structural match —
7//! variables behave as wildcards that can bind to any term, and consistent
8//! bindings across multiple patterns are intersected by shared variable name.
9
10use std::collections::HashMap;
11
12use crate::update_protocol_types::{
13    ArqError, ClearType, DropType, PatternTerm, SparqlUpdate, Triple, TriplePattern, UpdateResult,
14};
15
16// ---------------------------------------------------------------------------
17// In-memory UpdateExecutor
18// ---------------------------------------------------------------------------
19
20/// A minimal in-memory dataset executor for SPARQL 1.1 Update.
21///
22/// It maintains a *default graph* (a `Vec<Triple>`) and an arbitrary number
23/// of *named graphs* keyed by IRI string.  Pattern-based operations perform a
24/// simple structural match (no unification — variables are left as wildcards
25/// that match anything).
26pub struct UpdateExecutor {
27    /// Triples in the default graph.
28    pub(crate) triples: Vec<Triple>,
29    /// Named graphs keyed by IRI.
30    pub(crate) named_graphs: HashMap<String, Vec<Triple>>,
31}
32
33impl UpdateExecutor {
34    /// Create an empty executor.
35    pub fn new() -> Self {
36        Self {
37            triples: Vec::new(),
38            named_graphs: HashMap::new(),
39        }
40    }
41
42    /// Execute a single update and return a summary.
43    pub fn execute(&mut self, update: &SparqlUpdate) -> Result<UpdateResult, ArqError> {
44        match update {
45            SparqlUpdate::InsertData(triples) => {
46                let count = triples.len();
47                self.triples.extend(triples.iter().cloned());
48                Ok(UpdateResult {
49                    triples_inserted: count,
50                    triples_deleted: 0,
51                    graphs_affected: 0,
52                })
53            }
54            SparqlUpdate::DeleteData(triples) => {
55                let before = self.triples.len();
56                for t in triples {
57                    self.triples.retain(|existing| existing != t);
58                }
59                let deleted = before - self.triples.len();
60                Ok(UpdateResult {
61                    triples_inserted: 0,
62                    triples_deleted: deleted,
63                    graphs_affected: 0,
64                })
65            }
66            SparqlUpdate::InsertWhere {
67                template,
68                where_clause,
69            } => {
70                let bindings = self.match_patterns(where_clause);
71                let mut inserted = 0usize;
72                for binding in &bindings {
73                    if let Some(triple) = instantiate_template_triple(template.first(), binding) {
74                        for t in &triple {
75                            if !self.triples.contains(t) {
76                                self.triples.push(t.clone());
77                                inserted += 1;
78                            }
79                        }
80                    }
81                }
82                Ok(UpdateResult {
83                    triples_inserted: inserted,
84                    triples_deleted: 0,
85                    graphs_affected: 0,
86                })
87            }
88            SparqlUpdate::DeleteWhere { where_clause, .. } => {
89                let bindings = self.match_patterns(where_clause);
90                let to_delete: Vec<Triple> = bindings
91                    .into_iter()
92                    .filter_map(|b| {
93                        let s = b.get("s").cloned()?;
94                        let p = b.get("p").cloned()?;
95                        let o = b.get("o").cloned()?;
96                        Some(Triple::new(s, p, o))
97                    })
98                    .collect();
99                let before = self.triples.len();
100                for t in &to_delete {
101                    self.triples.retain(|e| e != t);
102                }
103                let deleted = before - self.triples.len();
104                Ok(UpdateResult {
105                    triples_inserted: 0,
106                    triples_deleted: deleted,
107                    graphs_affected: 0,
108                })
109            }
110            SparqlUpdate::Modify {
111                delete,
112                insert,
113                where_clause,
114            } => {
115                let bindings = self.match_patterns(where_clause);
116                let mut inserted = 0usize;
117                let mut deleted_count = 0usize;
118                for binding in &bindings {
119                    // Delete first.
120                    for tp in delete {
121                        if let Some(t) = instantiate_one(tp, binding) {
122                            let before = self.triples.len();
123                            self.triples.retain(|e| e != &t);
124                            deleted_count += before - self.triples.len();
125                        }
126                    }
127                    // Then insert.
128                    for tp in insert {
129                        if let Some(t) = instantiate_one(tp, binding) {
130                            if !self.triples.contains(&t) {
131                                self.triples.push(t);
132                                inserted += 1;
133                            }
134                        }
135                    }
136                }
137                Ok(UpdateResult {
138                    triples_inserted: inserted,
139                    triples_deleted: deleted_count,
140                    graphs_affected: 0,
141                })
142            }
143            SparqlUpdate::CreateGraph { iri, silent } => {
144                if self.named_graphs.contains_key(iri) && !silent {
145                    return Err(ArqError(format!("graph <{iri}> already exists")));
146                }
147                self.named_graphs.entry(iri.clone()).or_default();
148                Ok(UpdateResult {
149                    triples_inserted: 0,
150                    triples_deleted: 0,
151                    graphs_affected: 1,
152                })
153            }
154            SparqlUpdate::DropGraph {
155                iri,
156                silent,
157                drop_type,
158            } => {
159                let count = match drop_type {
160                    DropType::Graph => {
161                        let key = iri.as_deref().unwrap_or("");
162                        if self.named_graphs.remove(key).is_none() && !silent {
163                            return Err(ArqError(format!("graph <{key}> does not exist")));
164                        }
165                        1
166                    }
167                    DropType::Default => {
168                        self.triples.clear();
169                        1
170                    }
171                    DropType::Named => {
172                        let count = self.named_graphs.len();
173                        self.named_graphs.clear();
174                        count
175                    }
176                    DropType::All => {
177                        let ng = self.named_graphs.len();
178                        self.named_graphs.clear();
179                        self.triples.clear();
180                        ng + 1
181                    }
182                };
183                Ok(UpdateResult {
184                    triples_inserted: 0,
185                    triples_deleted: 0,
186                    graphs_affected: count,
187                })
188            }
189            SparqlUpdate::ClearGraph {
190                iri,
191                silent,
192                clear_type,
193            } => {
194                let count = match clear_type {
195                    ClearType::Graph => {
196                        let key = iri.as_deref().unwrap_or("");
197                        match self.named_graphs.get_mut(key) {
198                            Some(g) => {
199                                g.clear();
200                                1
201                            }
202                            None if *silent => 0,
203                            None => return Err(ArqError(format!("graph <{key}> does not exist"))),
204                        }
205                    }
206                    ClearType::Default => {
207                        self.triples.clear();
208                        1
209                    }
210                    ClearType::Named => {
211                        for g in self.named_graphs.values_mut() {
212                            g.clear();
213                        }
214                        self.named_graphs.len()
215                    }
216                    ClearType::All => {
217                        self.triples.clear();
218                        for g in self.named_graphs.values_mut() {
219                            g.clear();
220                        }
221                        self.named_graphs.len() + 1
222                    }
223                };
224                Ok(UpdateResult {
225                    triples_inserted: 0,
226                    triples_deleted: 0,
227                    graphs_affected: count,
228                })
229            }
230            SparqlUpdate::CopyGraph {
231                source,
232                target,
233                silent: _,
234            } => {
235                let src_triples: Vec<Triple> =
236                    self.named_graphs.get(source).cloned().unwrap_or_default();
237                let count = src_triples.len();
238                let tgt = self.named_graphs.entry(target.clone()).or_default();
239                tgt.clear();
240                tgt.extend(src_triples);
241                Ok(UpdateResult {
242                    triples_inserted: count,
243                    triples_deleted: 0,
244                    graphs_affected: 1,
245                })
246            }
247            SparqlUpdate::MoveGraph {
248                source,
249                target,
250                silent: _,
251            } => {
252                let src_triples = self.named_graphs.remove(source).unwrap_or_default();
253                let count = src_triples.len();
254                let tgt = self.named_graphs.entry(target.clone()).or_default();
255                tgt.clear();
256                tgt.extend(src_triples);
257                Ok(UpdateResult {
258                    triples_inserted: count,
259                    triples_deleted: 0,
260                    graphs_affected: 2,
261                })
262            }
263            SparqlUpdate::AddGraph {
264                source,
265                target,
266                silent: _,
267            } => {
268                let src_triples: Vec<Triple> =
269                    self.named_graphs.get(source).cloned().unwrap_or_default();
270                let count = src_triples.len();
271                let tgt = self.named_graphs.entry(target.clone()).or_default();
272                tgt.extend(src_triples);
273                Ok(UpdateResult {
274                    triples_inserted: count,
275                    triples_deleted: 0,
276                    graphs_affected: 1,
277                })
278            }
279            SparqlUpdate::Load { iri, into, silent } => {
280                // Actual HTTP loading is not implemented in this in-memory executor.
281                // Return success (silent) or error (non-silent).
282                if *silent {
283                    Ok(UpdateResult::default())
284                } else {
285                    Err(ArqError(format!(
286                        "LOAD is not supported in the in-memory executor (iri=<{iri}>, into={into:?})"
287                    )))
288                }
289            }
290        }
291    }
292
293    /// Execute a sequence of update operations and collect their results.
294    pub fn execute_all(&mut self, updates: &[SparqlUpdate]) -> Result<Vec<UpdateResult>, ArqError> {
295        updates.iter().map(|u| self.execute(u)).collect()
296    }
297
298    /// Number of triples in the default graph.
299    pub fn triple_count(&self) -> usize {
300        self.triples.len()
301    }
302
303    /// Number of named graphs (not counting the default graph).
304    pub fn graph_count(&self) -> usize {
305        self.named_graphs.len()
306    }
307
308    /// Return the triples in a named graph, or `None` if it does not exist.
309    pub fn get_graph(&self, iri: &str) -> Option<&Vec<Triple>> {
310        self.named_graphs.get(iri)
311    }
312
313    /// Return a reference to the default graph's triple set.
314    pub fn default_graph(&self) -> &Vec<Triple> {
315        &self.triples
316    }
317}
318
319impl Default for UpdateExecutor {
320    fn default() -> Self {
321        Self::new()
322    }
323}
324
325// ---------------------------------------------------------------------------
326// Pattern matching helpers
327// ---------------------------------------------------------------------------
328
329pub(crate) type Binding = HashMap<String, String>;
330
331/// Match a slice of triple patterns against the default graph, returning all
332/// consistent variable bindings.  Each pattern is matched independently and
333/// bindings from consecutive patterns are intersected by joining on shared
334/// variable names.
335fn match_patterns(triples: &[Triple], patterns: &[TriplePattern]) -> Vec<Binding> {
336    let mut results: Vec<Binding> = vec![HashMap::new()];
337
338    for pattern in patterns {
339        let mut next: Vec<Binding> = Vec::new();
340        for binding in &results {
341            for triple in triples {
342                if let Some(new_binding) = match_pattern(triple, pattern, binding) {
343                    next.push(new_binding);
344                }
345            }
346        }
347        results = next;
348    }
349
350    results
351}
352
353/// Try to extend `existing_binding` with the variable bindings produced by
354/// matching `triple` against `pattern`.  Returns `None` on conflict.
355fn match_pattern(triple: &Triple, pattern: &TriplePattern, existing: &Binding) -> Option<Binding> {
356    let mut binding = existing.clone();
357    bind_term(&triple.s, &pattern.s, &mut binding)?;
358    bind_term(&triple.p, &pattern.p, &mut binding)?;
359    bind_term(&triple.o, &pattern.o, &mut binding)?;
360    Some(binding)
361}
362
363/// Attempt to bind `value` against `term`, extending `binding` if `term` is a
364/// variable.  Returns `None` when an existing binding is inconsistent.
365fn bind_term(value: &str, term: &PatternTerm, binding: &mut Binding) -> Option<()> {
366    match term {
367        PatternTerm::Variable(var) => {
368            if let Some(existing) = binding.get(var.as_str()) {
369                if existing != value {
370                    return None;
371                }
372            } else {
373                binding.insert(var.clone(), value.to_string());
374            }
375            Some(())
376        }
377        PatternTerm::Iri(iri) => {
378            if iri == value {
379                Some(())
380            } else {
381                None
382            }
383        }
384        PatternTerm::Literal(lit) => {
385            // Compare the content without surrounding quotes.
386            let inner = lit.trim_matches('"').trim_matches('\'');
387            if inner == value || lit == value {
388                Some(())
389            } else {
390                None
391            }
392        }
393        PatternTerm::BlankNode(bn) => {
394            if bn == value {
395                Some(())
396            } else {
397                None
398            }
399        }
400    }
401}
402
403impl UpdateExecutor {
404    /// Match patterns against the default graph's triple set.
405    fn match_patterns(&self, patterns: &[TriplePattern]) -> Vec<Binding> {
406        match_patterns(&self.triples, patterns)
407    }
408}
409
410/// Try to instantiate a single `TriplePattern` against a `Binding`, producing
411/// a `Triple` when all positions resolve to concrete terms.
412fn instantiate_one(pattern: &TriplePattern, binding: &Binding) -> Option<Triple> {
413    let s = resolve_term(&pattern.s, binding)?;
414    let p = resolve_term(&pattern.p, binding)?;
415    let o = resolve_term(&pattern.o, binding)?;
416    Some(Triple::new(s, p, o))
417}
418
419/// Try to instantiate the first `TriplePattern` in `templates`, returning a
420/// `Vec<Triple>` (0 or 1 elements).  This helper is used for `InsertWhere`.
421fn instantiate_template_triple(
422    template: Option<&TriplePattern>,
423    binding: &Binding,
424) -> Option<Vec<Triple>> {
425    let tp = template?;
426    Some(instantiate_one(tp, binding).into_iter().collect())
427}
428
429/// Resolve a `PatternTerm` to a concrete string using `binding`.  Returns
430/// `None` when a variable is unbound.
431fn resolve_term(term: &PatternTerm, binding: &Binding) -> Option<String> {
432    match term {
433        PatternTerm::Variable(var) => binding.get(var.as_str()).cloned(),
434        PatternTerm::Iri(iri) => Some(iri.clone()),
435        PatternTerm::Literal(lit) => Some(lit.trim_matches('"').trim_matches('\'').to_string()),
436        PatternTerm::BlankNode(bn) => Some(bn.clone()),
437    }
438}