Skip to main content

oxirs_arq/
update_protocol.rs

1//! SPARQL 1.1 Update Protocol — standalone parser and in-memory executor
2//!
3//! This module provides a self-contained representation of all SPARQL 1.1
4//! graph-update operations together with a text parser and an in-memory
5//! store-backed executor.  It is intentionally independent of the
6//! store-coupled `UpdateExecutor` in `update.rs` so that it can be used in
7//! contexts where only an in-memory triple set is required (e.g. unit tests
8//! and HTTP protocol adapters).
9//!
10//! ## Supported operations
11//!
12//! | SPARQL keyword | Variant |
13//! |---|---|
14//! | `INSERT DATA` | `SparqlUpdate::InsertData` |
15//! | `DELETE DATA` | `SparqlUpdate::DeleteData` |
16//! | `INSERT { … } WHERE { … }` | `SparqlUpdate::InsertWhere` |
17//! | `DELETE { … } WHERE { … }` | `SparqlUpdate::DeleteWhere` |
18//! | `DELETE { … } INSERT { … } WHERE { … }` | `SparqlUpdate::Modify` |
19//! | `CREATE [SILENT] GRAPH <…>` | `SparqlUpdate::CreateGraph` |
20//! | `DROP [SILENT] [GRAPH <…> \| DEFAULT \| NAMED \| ALL]` | `SparqlUpdate::DropGraph` |
21//! | `CLEAR [SILENT] [GRAPH <…> \| DEFAULT \| NAMED \| ALL]` | `SparqlUpdate::ClearGraph` |
22//! | `COPY [SILENT] <…> TO <…>` | `SparqlUpdate::CopyGraph` |
23//! | `MOVE [SILENT] <…> TO <…>` | `SparqlUpdate::MoveGraph` |
24//! | `ADD [SILENT] <…> TO <…>` | `SparqlUpdate::AddGraph` |
25//! | `LOAD [SILENT] <…> [INTO GRAPH <…>]` | `SparqlUpdate::Load` |
26
27use std::collections::HashMap;
28
29// ---------------------------------------------------------------------------
30// Domain types
31// ---------------------------------------------------------------------------
32
33/// A concrete RDF triple (no variables).
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub struct Triple {
36    pub s: String,
37    pub p: String,
38    pub o: String,
39}
40
41impl Triple {
42    /// Convenience constructor.
43    pub fn new(s: impl Into<String>, p: impl Into<String>, o: impl Into<String>) -> Self {
44        Self {
45            s: s.into(),
46            p: p.into(),
47            o: o.into(),
48        }
49    }
50}
51
52/// A position in a triple pattern – can be an IRI, a plain literal, a blank
53/// node, or a variable (placeholder for pattern matching).
54#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub enum PatternTerm {
56    Iri(String),
57    Literal(String),
58    Variable(String),
59    BlankNode(String),
60}
61
62impl PatternTerm {
63    /// Returns `true` when this term is a variable (used during template instantiation).
64    pub fn is_variable(&self) -> bool {
65        matches!(self, PatternTerm::Variable(_))
66    }
67
68    /// Returns the variable name if this is a `Variable` variant.
69    pub fn variable_name(&self) -> Option<&str> {
70        if let PatternTerm::Variable(name) = self {
71            Some(name.as_str())
72        } else {
73            None
74        }
75    }
76}
77
78/// A triple pattern where any position may be a variable.
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct TriplePattern {
81    pub s: PatternTerm,
82    pub p: PatternTerm,
83    pub o: PatternTerm,
84}
85
86impl TriplePattern {
87    /// Construct a new triple pattern.
88    pub fn new(s: PatternTerm, p: PatternTerm, o: PatternTerm) -> Self {
89        Self { s, o, p }
90    }
91}
92
93// ---------------------------------------------------------------------------
94// DROP / CLEAR target type
95// ---------------------------------------------------------------------------
96
97/// Scope qualifier for `DROP` operations.
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum DropType {
100    /// A specific named graph identified by IRI.
101    Graph,
102    /// The default graph.
103    Default,
104    /// All named graphs.
105    Named,
106    /// Every graph in the dataset (default + all named).
107    All,
108}
109
110/// Scope qualifier for `CLEAR` operations.
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum ClearType {
113    /// A specific named graph identified by IRI.
114    Graph,
115    /// The default graph.
116    Default,
117    /// All named graphs.
118    Named,
119    /// Every graph in the dataset.
120    All,
121}
122
123// ---------------------------------------------------------------------------
124// Top-level update enum
125// ---------------------------------------------------------------------------
126
127/// A single SPARQL 1.1 update operation.
128#[derive(Debug, Clone, PartialEq)]
129pub enum SparqlUpdate {
130    /// `INSERT DATA { … }` — adds concrete triples to the default graph.
131    InsertData(Vec<Triple>),
132
133    /// `DELETE DATA { … }` — removes concrete triples from the default graph.
134    DeleteData(Vec<Triple>),
135
136    /// `INSERT { template } WHERE { where_clause }` — pattern-based insert.
137    InsertWhere {
138        template: Vec<TriplePattern>,
139        where_clause: Vec<TriplePattern>,
140    },
141
142    /// `DELETE { template } WHERE { where_clause }` — pattern-based delete.
143    DeleteWhere {
144        template: Vec<TriplePattern>,
145        where_clause: Vec<TriplePattern>,
146    },
147
148    /// `DELETE { delete } INSERT { insert } WHERE { where_clause }` — combined modify.
149    Modify {
150        delete: Vec<TriplePattern>,
151        insert: Vec<TriplePattern>,
152        where_clause: Vec<TriplePattern>,
153    },
154
155    /// `CREATE [SILENT] GRAPH <iri>`.
156    CreateGraph { iri: String, silent: bool },
157
158    /// `DROP [SILENT] (GRAPH <iri> | DEFAULT | NAMED | ALL)`.
159    DropGraph {
160        iri: Option<String>,
161        silent: bool,
162        drop_type: DropType,
163    },
164
165    /// `CLEAR [SILENT] (GRAPH <iri> | DEFAULT | NAMED | ALL)`.
166    ClearGraph {
167        iri: Option<String>,
168        silent: bool,
169        clear_type: ClearType,
170    },
171
172    /// `COPY [SILENT] <source> TO <target>`.
173    CopyGraph {
174        source: String,
175        target: String,
176        silent: bool,
177    },
178
179    /// `MOVE [SILENT] <source> TO <target>`.
180    MoveGraph {
181        source: String,
182        target: String,
183        silent: bool,
184    },
185
186    /// `ADD [SILENT] <source> TO <target>`.
187    AddGraph {
188        source: String,
189        target: String,
190        silent: bool,
191    },
192
193    /// `LOAD [SILENT] <iri> [INTO GRAPH <into>]`.
194    Load {
195        iri: String,
196        into: Option<String>,
197        silent: bool,
198    },
199}
200
201// ---------------------------------------------------------------------------
202// Parse error
203// ---------------------------------------------------------------------------
204
205/// Error returned by `SparqlUpdateParser`.
206#[derive(Debug, Clone, PartialEq)]
207pub struct ParseError {
208    pub message: String,
209    /// Byte offset inside the input string where the error was detected.
210    pub position: usize,
211}
212
213impl ParseError {
214    fn at(position: usize, message: impl Into<String>) -> Self {
215        Self {
216            message: message.into(),
217            position,
218        }
219    }
220}
221
222impl std::fmt::Display for ParseError {
223    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224        write!(
225            f,
226            "parse error at position {}: {}",
227            self.position, self.message
228        )
229    }
230}
231
232impl std::error::Error for ParseError {}
233
234// ---------------------------------------------------------------------------
235// Parser
236// ---------------------------------------------------------------------------
237
238/// A lightweight tokenising parser for SPARQL 1.1 Update text.
239///
240/// The parser is intentionally simple (hand-written recursive descent on a
241/// token stream) and covers the most common production patterns.  It does not
242/// handle prefix declarations (`PREFIX`) or the `BASE` directive — callers
243/// that require prefix resolution should expand prefixes before passing the
244/// string to the parser.
245pub struct SparqlUpdateParser;
246
247impl SparqlUpdateParser {
248    /// Parse zero or more semicolon-separated update operations from `input`.
249    pub fn parse(input: &str) -> Result<Vec<SparqlUpdate>, ParseError> {
250        let tokens = tokenise(input);
251        let mut cursor = 0usize;
252        let mut results = Vec::new();
253
254        // Skip optional leading PREFIX declarations.
255        skip_prefixes(&tokens, &mut cursor);
256
257        while cursor < tokens.len() {
258            skip_prefixes(&tokens, &mut cursor);
259            if cursor >= tokens.len() {
260                break;
261            }
262            let update = parse_one_operation(&tokens, &mut cursor)?;
263            results.push(update);
264            // Consume optional ';' separator.
265            if cursor < tokens.len() && tokens[cursor] == ";" {
266                cursor += 1;
267            }
268        }
269
270        Ok(results)
271    }
272
273    /// Parse exactly one update operation from `input`.
274    pub fn parse_one(input: &str) -> Result<SparqlUpdate, ParseError> {
275        let mut updates = Self::parse(input)?;
276        match updates.len() {
277            0 => Err(ParseError::at(0, "no update operation found")),
278            1 => Ok(updates.remove(0)),
279            n => Err(ParseError::at(
280                0,
281                format!("expected exactly one operation, found {n}"),
282            )),
283        }
284    }
285}
286
287// ---------------------------------------------------------------------------
288// Tokeniser
289// ---------------------------------------------------------------------------
290
291/// Produce a flat vector of tokens from a SPARQL update string.
292///
293/// Tokens are: keywords, IRIs (`<…>`), string literals (`"…"` / `'…'`),
294/// blank node labels (`_:…`), variables (`?…` / `$…`), punctuation, and
295/// bare identifiers.  Whitespace and `# comments` are discarded.
296fn tokenise(input: &str) -> Vec<String> {
297    let mut tokens = Vec::new();
298    let chars: Vec<char> = input.chars().collect();
299    let mut i = 0;
300
301    while i < chars.len() {
302        match chars[i] {
303            // Skip whitespace
304            c if c.is_whitespace() => i += 1,
305            // Line comment
306            '#' => {
307                while i < chars.len() && chars[i] != '\n' {
308                    i += 1;
309                }
310            }
311            // IRI reference <…>
312            '<' => {
313                let mut tok = String::from('<');
314                i += 1;
315                while i < chars.len() && chars[i] != '>' {
316                    tok.push(chars[i]);
317                    i += 1;
318                }
319                if i < chars.len() {
320                    tok.push('>');
321                    i += 1;
322                }
323                tokens.push(tok);
324            }
325            // Double-quoted literal
326            '"' => {
327                let mut tok = String::from('"');
328                i += 1;
329                while i < chars.len() && chars[i] != '"' {
330                    if chars[i] == '\\' && i + 1 < chars.len() {
331                        tok.push(chars[i]);
332                        i += 1;
333                    }
334                    tok.push(chars[i]);
335                    i += 1;
336                }
337                if i < chars.len() {
338                    tok.push('"');
339                    i += 1;
340                }
341                tokens.push(tok);
342            }
343            // Single-quoted literal
344            '\'' => {
345                let mut tok = String::from('\'');
346                i += 1;
347                while i < chars.len() && chars[i] != '\'' {
348                    if chars[i] == '\\' && i + 1 < chars.len() {
349                        tok.push(chars[i]);
350                        i += 1;
351                    }
352                    tok.push(chars[i]);
353                    i += 1;
354                }
355                if i < chars.len() {
356                    tok.push('\'');
357                    i += 1;
358                }
359                tokens.push(tok);
360            }
361            // Variable ?name or $name
362            '?' | '$' => {
363                let mut tok = String::from('?');
364                i += 1;
365                while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
366                    tok.push(chars[i]);
367                    i += 1;
368                }
369                tokens.push(tok);
370            }
371            // Punctuation: { } ( ) . , ; ^^ @
372            '{' | '}' | '(' | ')' | '.' | ',' | ';' => {
373                tokens.push(chars[i].to_string());
374                i += 1;
375            }
376            // ^^  datatype marker or ^ inverse path
377            '^' => {
378                if i + 1 < chars.len() && chars[i + 1] == '^' {
379                    tokens.push("^^".to_string());
380                    i += 2;
381                } else {
382                    tokens.push("^".to_string());
383                    i += 1;
384                }
385            }
386            // @ language tag
387            '@' => {
388                let mut tok = String::from('@');
389                i += 1;
390                while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '-') {
391                    tok.push(chars[i]);
392                    i += 1;
393                }
394                tokens.push(tok);
395            }
396            // Bare identifier, keyword, prefixed name, or blank node label
397            c if c.is_alphabetic() || c == '_' => {
398                let mut tok = String::new();
399                while i < chars.len()
400                    && (chars[i].is_alphanumeric()
401                        || chars[i] == '_'
402                        || chars[i] == ':'
403                        || chars[i] == '-')
404                {
405                    tok.push(chars[i]);
406                    i += 1;
407                }
408                tokens.push(tok);
409            }
410            // Numbers / other characters — collect until whitespace or punctuation
411            _ => {
412                let mut tok = String::new();
413                while i < chars.len()
414                    && !chars[i].is_whitespace()
415                    && !matches!(chars[i], '{' | '}' | '(' | ')' | ',' | ';')
416                {
417                    tok.push(chars[i]);
418                    i += 1;
419                }
420                if !tok.is_empty() {
421                    tokens.push(tok);
422                }
423            }
424        }
425    }
426
427    tokens
428}
429
430// ---------------------------------------------------------------------------
431// Parser helpers
432// ---------------------------------------------------------------------------
433
434fn peek(tokens: &[String], cursor: usize) -> Option<&str> {
435    tokens.get(cursor).map(|s| s.as_str())
436}
437
438fn expect<'a>(
439    tokens: &'a [String],
440    cursor: &mut usize,
441    expected: &str,
442) -> Result<&'a str, ParseError> {
443    match tokens.get(*cursor) {
444        Some(tok) if tok.to_uppercase() == expected.to_uppercase() => {
445            *cursor += 1;
446            Ok(tok.as_str())
447        }
448        Some(tok) => Err(ParseError::at(
449            *cursor,
450            format!("expected '{expected}', found '{tok}'"),
451        )),
452        None => Err(ParseError::at(
453            *cursor,
454            format!("expected '{expected}', found end of input"),
455        )),
456    }
457}
458
459fn consume_keyword(tokens: &[String], cursor: &mut usize, keyword: &str) -> bool {
460    match tokens.get(*cursor) {
461        Some(tok) if tok.to_uppercase() == keyword.to_uppercase() => {
462            *cursor += 1;
463            true
464        }
465        _ => false,
466    }
467}
468
469/// Consume the next token unconditionally and return it.
470fn consume(tokens: &[String], cursor: &mut usize) -> Result<String, ParseError> {
471    tokens
472        .get(*cursor)
473        .map(|t| {
474            *cursor += 1;
475            t.clone()
476        })
477        .ok_or_else(|| ParseError::at(*cursor, "unexpected end of input"))
478}
479
480/// Parse an IRI token of the form `<…>` and return the inner string.
481fn parse_iri(tokens: &[String], cursor: &mut usize) -> Result<String, ParseError> {
482    match tokens.get(*cursor) {
483        Some(tok) if tok.starts_with('<') && tok.ends_with('>') => {
484            let iri = tok[1..tok.len() - 1].to_string();
485            *cursor += 1;
486            Ok(iri)
487        }
488        Some(tok) => Err(ParseError::at(
489            *cursor,
490            format!("expected IRI, found '{tok}'"),
491        )),
492        None => Err(ParseError::at(*cursor, "expected IRI, found end of input")),
493    }
494}
495
496/// Parse an optional `SILENT` keyword and return whether it was present.
497fn parse_silent(tokens: &[String], cursor: &mut usize) -> bool {
498    consume_keyword(tokens, cursor, "SILENT")
499}
500
501/// Skip zero or more `PREFIX` declarations.
502fn skip_prefixes(tokens: &[String], cursor: &mut usize) {
503    while let Some(tok) = tokens.get(*cursor) {
504        if tok.to_uppercase() != "PREFIX" {
505            break;
506        }
507        *cursor += 1; // consume PREFIX
508                      // prefix name (e.g. "ex:" or ":")
509        *cursor += 1;
510        // IRI
511        *cursor += 1;
512    }
513}
514
515// ---------------------------------------------------------------------------
516// Triple / pattern parsing
517// ---------------------------------------------------------------------------
518
519/// Parse a set of concrete triples inside `{ … }`.
520/// Triples may be separated by `.` or `;` (simplified Turtle-like syntax).
521fn parse_triple_block(tokens: &[String], cursor: &mut usize) -> Result<Vec<Triple>, ParseError> {
522    expect(tokens, cursor, "{")?;
523    let mut triples = Vec::new();
524
525    while let Some(tok) = tokens.get(*cursor) {
526        if tok == "}" {
527            break;
528        }
529        if tok == "." {
530            *cursor += 1;
531            continue;
532        }
533
534        let s = parse_term_str(tokens, cursor)?;
535        let p = parse_term_str(tokens, cursor)?;
536        let o = parse_term_str(tokens, cursor)?;
537        triples.push(Triple::new(s, p, o));
538
539        // Optional trailing dot.
540        if matches!(peek(tokens, *cursor), Some(".")) {
541            *cursor += 1;
542        }
543    }
544
545    expect(tokens, cursor, "}")?;
546    Ok(triples)
547}
548
549/// Parse a set of triple patterns (may contain variables) inside `{ … }`.
550fn parse_pattern_block(
551    tokens: &[String],
552    cursor: &mut usize,
553) -> Result<Vec<TriplePattern>, ParseError> {
554    expect(tokens, cursor, "{")?;
555    let mut patterns = Vec::new();
556
557    while let Some(tok) = tokens.get(*cursor) {
558        if tok == "}" {
559            break;
560        }
561        if tok == "." {
562            *cursor += 1;
563            continue;
564        }
565
566        let s = parse_pattern_term(tokens, cursor)?;
567        let p = parse_pattern_term(tokens, cursor)?;
568        let o = parse_pattern_term(tokens, cursor)?;
569        patterns.push(TriplePattern::new(s, p, o));
570
571        if matches!(peek(tokens, *cursor), Some(".")) {
572            *cursor += 1;
573        }
574    }
575
576    expect(tokens, cursor, "}")?;
577    Ok(patterns)
578}
579
580/// Parse a single term as a plain string for use in concrete triples.
581fn parse_term_str(tokens: &[String], cursor: &mut usize) -> Result<String, ParseError> {
582    let tok = consume(tokens, cursor)?;
583    // Unwrap IRI angles.
584    if tok.starts_with('<') && tok.ends_with('>') {
585        return Ok(tok[1..tok.len() - 1].to_string());
586    }
587    Ok(tok)
588}
589
590/// Parse a single term into a `PatternTerm`.
591fn parse_pattern_term(tokens: &[String], cursor: &mut usize) -> Result<PatternTerm, ParseError> {
592    let tok = consume(tokens, cursor)?;
593
594    // Variable: ?name
595    if let Some(stripped) = tok.strip_prefix('?') {
596        return Ok(PatternTerm::Variable(stripped.to_string()));
597    }
598
599    // IRI: <…>
600    if tok.starts_with('<') && tok.ends_with('>') {
601        return Ok(PatternTerm::Iri(tok[1..tok.len() - 1].to_string()));
602    }
603
604    // Blank node: _:label
605    if let Some(stripped) = tok.strip_prefix("_:") {
606        return Ok(PatternTerm::BlankNode(stripped.to_string()));
607    }
608
609    // Literal: "…"
610    if tok.starts_with('"') || tok.starts_with('\'') {
611        // Consume optional @lang or ^^datatype.
612        if matches!(peek(tokens, *cursor), Some(t) if t.starts_with('@')) {
613            let _lang = consume(tokens, cursor)?;
614        } else if matches!(peek(tokens, *cursor), Some("^^")) {
615            *cursor += 1; // skip ^^
616            let _dt = consume(tokens, cursor)?;
617        }
618        return Ok(PatternTerm::Literal(tok));
619    }
620
621    // Prefixed name or keyword treated as IRI-like.
622    Ok(PatternTerm::Iri(tok))
623}
624
625// ---------------------------------------------------------------------------
626// Top-level operation parser
627// ---------------------------------------------------------------------------
628
629fn parse_one_operation(tokens: &[String], cursor: &mut usize) -> Result<SparqlUpdate, ParseError> {
630    let keyword = match tokens.get(*cursor) {
631        Some(k) => k.to_uppercase(),
632        None => return Err(ParseError::at(*cursor, "unexpected end of input")),
633    };
634
635    match keyword.as_str() {
636        "INSERT" => {
637            *cursor += 1;
638            if matches!(peek(tokens, *cursor), Some(t) if t.to_uppercase() == "DATA") {
639                *cursor += 1;
640                let triples = parse_triple_block(tokens, cursor)?;
641                Ok(SparqlUpdate::InsertData(triples))
642            } else {
643                // INSERT { template } WHERE { pattern }
644                let template = parse_pattern_block(tokens, cursor)?;
645                expect(tokens, cursor, "WHERE")?;
646                let where_clause = parse_pattern_block(tokens, cursor)?;
647                Ok(SparqlUpdate::InsertWhere {
648                    template,
649                    where_clause,
650                })
651            }
652        }
653        "DELETE" => {
654            *cursor += 1;
655            if matches!(peek(tokens, *cursor), Some(t) if t.to_uppercase() == "DATA") {
656                *cursor += 1;
657                let triples = parse_triple_block(tokens, cursor)?;
658                Ok(SparqlUpdate::DeleteData(triples))
659            } else if matches!(peek(tokens, *cursor), Some(t) if t.to_uppercase() == "WHERE") {
660                // DELETE WHERE { pattern }
661                *cursor += 1;
662                let where_clause = parse_pattern_block(tokens, cursor)?;
663                Ok(SparqlUpdate::DeleteWhere {
664                    template: vec![],
665                    where_clause,
666                })
667            } else {
668                // DELETE { del_template } [INSERT { ins_template }] WHERE { pattern }
669                let delete_template = parse_pattern_block(tokens, cursor)?;
670                let insert_template = if matches!(peek(tokens, *cursor), Some(t) if t.to_uppercase() == "INSERT")
671                {
672                    *cursor += 1;
673                    parse_pattern_block(tokens, cursor)?
674                } else {
675                    vec![]
676                };
677                expect(tokens, cursor, "WHERE")?;
678                let where_clause = parse_pattern_block(tokens, cursor)?;
679                Ok(SparqlUpdate::Modify {
680                    delete: delete_template,
681                    insert: insert_template,
682                    where_clause,
683                })
684            }
685        }
686        "CREATE" => {
687            *cursor += 1;
688            let silent = parse_silent(tokens, cursor);
689            consume_keyword(tokens, cursor, "GRAPH");
690            let iri = parse_iri(tokens, cursor)?;
691            Ok(SparqlUpdate::CreateGraph { iri, silent })
692        }
693        "DROP" => {
694            *cursor += 1;
695            let silent = parse_silent(tokens, cursor);
696            parse_graph_target_update(tokens, cursor, |iri, drop_type| SparqlUpdate::DropGraph {
697                iri,
698                silent,
699                drop_type: drop_type.into_drop(),
700            })
701        }
702        "CLEAR" => {
703            *cursor += 1;
704            let silent = parse_silent(tokens, cursor);
705            parse_graph_target_update(tokens, cursor, |iri, clear_type| SparqlUpdate::ClearGraph {
706                iri,
707                silent,
708                clear_type: clear_type.into_clear(),
709            })
710        }
711        "COPY" => {
712            *cursor += 1;
713            let silent = parse_silent(tokens, cursor);
714            let source = parse_iri(tokens, cursor)?;
715            expect(tokens, cursor, "TO")?;
716            let target = parse_iri(tokens, cursor)?;
717            Ok(SparqlUpdate::CopyGraph {
718                source,
719                target,
720                silent,
721            })
722        }
723        "MOVE" => {
724            *cursor += 1;
725            let silent = parse_silent(tokens, cursor);
726            let source = parse_iri(tokens, cursor)?;
727            expect(tokens, cursor, "TO")?;
728            let target = parse_iri(tokens, cursor)?;
729            Ok(SparqlUpdate::MoveGraph {
730                source,
731                target,
732                silent,
733            })
734        }
735        "ADD" => {
736            *cursor += 1;
737            let silent = parse_silent(tokens, cursor);
738            let source = parse_iri(tokens, cursor)?;
739            expect(tokens, cursor, "TO")?;
740            let target = parse_iri(tokens, cursor)?;
741            Ok(SparqlUpdate::AddGraph {
742                source,
743                target,
744                silent,
745            })
746        }
747        "LOAD" => {
748            *cursor += 1;
749            let silent = parse_silent(tokens, cursor);
750            let iri = parse_iri(tokens, cursor)?;
751            let into = if consume_keyword(tokens, cursor, "INTO") {
752                consume_keyword(tokens, cursor, "GRAPH");
753                Some(parse_iri(tokens, cursor)?)
754            } else {
755                None
756            };
757            Ok(SparqlUpdate::Load { iri, into, silent })
758        }
759        other => Err(ParseError::at(
760            *cursor,
761            format!("unknown update operation keyword: '{other}'"),
762        )),
763    }
764}
765
766// ---------------------------------------------------------------------------
767// Graph target parsing helper (DROP / CLEAR share the same grammar)
768// ---------------------------------------------------------------------------
769
770/// Temporary enum for the parsed scope before converting to `DropType`/`ClearType`.
771enum GraphScope {
772    GraphIri,
773    Default,
774    Named,
775    All,
776}
777
778impl GraphScope {
779    fn into_drop(self) -> DropType {
780        match self {
781            GraphScope::GraphIri => DropType::Graph,
782            GraphScope::Default => DropType::Default,
783            GraphScope::Named => DropType::Named,
784            GraphScope::All => DropType::All,
785        }
786    }
787
788    fn into_clear(self) -> ClearType {
789        match self {
790            GraphScope::GraphIri => ClearType::Graph,
791            GraphScope::Default => ClearType::Default,
792            GraphScope::Named => ClearType::Named,
793            GraphScope::All => ClearType::All,
794        }
795    }
796}
797
798fn parse_graph_target_update<F>(
799    tokens: &[String],
800    cursor: &mut usize,
801    builder: F,
802) -> Result<SparqlUpdate, ParseError>
803where
804    F: FnOnce(Option<String>, GraphScope) -> SparqlUpdate,
805{
806    let keyword = tokens.get(*cursor).map(|t| t.to_uppercase());
807    match keyword.as_deref() {
808        Some("DEFAULT") => {
809            *cursor += 1;
810            let scope = GraphScope::Default;
811            Ok(builder(None, scope))
812        }
813        Some("NAMED") => {
814            *cursor += 1;
815            let scope = GraphScope::Named;
816            Ok(builder(None, scope))
817        }
818        Some("ALL") => {
819            *cursor += 1;
820            let scope = GraphScope::All;
821            Ok(builder(None, scope))
822        }
823        Some("GRAPH") => {
824            *cursor += 1;
825            let iri = parse_iri(tokens, cursor)?;
826            let scope = GraphScope::GraphIri;
827            Ok(builder(Some(iri), scope))
828        }
829        // Bare IRI without GRAPH keyword.
830        Some(tok) if tok.starts_with('<') => {
831            let iri = parse_iri(tokens, cursor)?;
832            let scope = GraphScope::GraphIri;
833            Ok(builder(Some(iri), scope))
834        }
835        _ => Err(ParseError::at(
836            *cursor,
837            "expected graph scope (DEFAULT | NAMED | ALL | GRAPH <iri>)",
838        )),
839    }
840}
841
842// ---------------------------------------------------------------------------
843// UpdateResult
844// ---------------------------------------------------------------------------
845
846/// Summary of the changes made by a single `UpdateExecutor::execute` call.
847#[derive(Debug, Clone, Default, PartialEq, Eq)]
848pub struct UpdateResult {
849    /// Number of triples inserted into the default graph or named graphs.
850    pub triples_inserted: usize,
851    /// Number of triples deleted from the default graph or named graphs.
852    pub triples_deleted: usize,
853    /// Number of distinct graphs affected (created, cleared, populated, etc.).
854    pub graphs_affected: usize,
855}
856
857// ---------------------------------------------------------------------------
858// ArqError (thin wrapper)
859// ---------------------------------------------------------------------------
860
861/// Error type for the update executor.
862#[derive(Debug, Clone, PartialEq)]
863pub struct ArqError(pub String);
864
865impl std::fmt::Display for ArqError {
866    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
867        write!(f, "ARQ error: {}", self.0)
868    }
869}
870
871impl std::error::Error for ArqError {}
872
873// ---------------------------------------------------------------------------
874// In-memory UpdateExecutor
875// ---------------------------------------------------------------------------
876
877/// A minimal in-memory dataset executor for SPARQL 1.1 Update.
878///
879/// It maintains a *default graph* (a `Vec<Triple>`) and an arbitrary number
880/// of *named graphs* keyed by IRI string.  Pattern-based operations perform a
881/// simple structural match (no unification — variables are left as wildcards
882/// that match anything).
883pub struct UpdateExecutor {
884    /// Triples in the default graph.
885    triples: Vec<Triple>,
886    /// Named graphs keyed by IRI.
887    named_graphs: HashMap<String, Vec<Triple>>,
888}
889
890impl UpdateExecutor {
891    /// Create an empty executor.
892    pub fn new() -> Self {
893        Self {
894            triples: Vec::new(),
895            named_graphs: HashMap::new(),
896        }
897    }
898
899    /// Execute a single update and return a summary.
900    pub fn execute(&mut self, update: &SparqlUpdate) -> Result<UpdateResult, ArqError> {
901        match update {
902            SparqlUpdate::InsertData(triples) => {
903                let count = triples.len();
904                self.triples.extend(triples.iter().cloned());
905                Ok(UpdateResult {
906                    triples_inserted: count,
907                    triples_deleted: 0,
908                    graphs_affected: 0,
909                })
910            }
911            SparqlUpdate::DeleteData(triples) => {
912                let before = self.triples.len();
913                for t in triples {
914                    self.triples.retain(|existing| existing != t);
915                }
916                let deleted = before - self.triples.len();
917                Ok(UpdateResult {
918                    triples_inserted: 0,
919                    triples_deleted: deleted,
920                    graphs_affected: 0,
921                })
922            }
923            SparqlUpdate::InsertWhere {
924                template,
925                where_clause,
926            } => {
927                let bindings = self.match_patterns(where_clause);
928                let mut inserted = 0usize;
929                for binding in &bindings {
930                    if let Some(triple) = instantiate_template_triple(template.first(), binding) {
931                        for t in &triple {
932                            if !self.triples.contains(t) {
933                                self.triples.push(t.clone());
934                                inserted += 1;
935                            }
936                        }
937                    }
938                }
939                Ok(UpdateResult {
940                    triples_inserted: inserted,
941                    triples_deleted: 0,
942                    graphs_affected: 0,
943                })
944            }
945            SparqlUpdate::DeleteWhere { where_clause, .. } => {
946                let bindings = self.match_patterns(where_clause);
947                let to_delete: Vec<Triple> = bindings
948                    .into_iter()
949                    .filter_map(|b| {
950                        let s = b.get("s").cloned()?;
951                        let p = b.get("p").cloned()?;
952                        let o = b.get("o").cloned()?;
953                        Some(Triple::new(s, p, o))
954                    })
955                    .collect();
956                let before = self.triples.len();
957                for t in &to_delete {
958                    self.triples.retain(|e| e != t);
959                }
960                let deleted = before - self.triples.len();
961                Ok(UpdateResult {
962                    triples_inserted: 0,
963                    triples_deleted: deleted,
964                    graphs_affected: 0,
965                })
966            }
967            SparqlUpdate::Modify {
968                delete,
969                insert,
970                where_clause,
971            } => {
972                let bindings = self.match_patterns(where_clause);
973                let mut inserted = 0usize;
974                let mut deleted_count = 0usize;
975                for binding in &bindings {
976                    // Delete first.
977                    for tp in delete {
978                        if let Some(t) = instantiate_one(tp, binding) {
979                            let before = self.triples.len();
980                            self.triples.retain(|e| e != &t);
981                            deleted_count += before - self.triples.len();
982                        }
983                    }
984                    // Then insert.
985                    for tp in insert {
986                        if let Some(t) = instantiate_one(tp, binding) {
987                            if !self.triples.contains(&t) {
988                                self.triples.push(t);
989                                inserted += 1;
990                            }
991                        }
992                    }
993                }
994                Ok(UpdateResult {
995                    triples_inserted: inserted,
996                    triples_deleted: deleted_count,
997                    graphs_affected: 0,
998                })
999            }
1000            SparqlUpdate::CreateGraph { iri, silent } => {
1001                if self.named_graphs.contains_key(iri) && !silent {
1002                    return Err(ArqError(format!("graph <{iri}> already exists")));
1003                }
1004                self.named_graphs.entry(iri.clone()).or_default();
1005                Ok(UpdateResult {
1006                    triples_inserted: 0,
1007                    triples_deleted: 0,
1008                    graphs_affected: 1,
1009                })
1010            }
1011            SparqlUpdate::DropGraph {
1012                iri,
1013                silent,
1014                drop_type,
1015            } => {
1016                let count = match drop_type {
1017                    DropType::Graph => {
1018                        let key = iri.as_deref().unwrap_or("");
1019                        if self.named_graphs.remove(key).is_none() && !silent {
1020                            return Err(ArqError(format!("graph <{key}> does not exist")));
1021                        }
1022                        1
1023                    }
1024                    DropType::Default => {
1025                        self.triples.clear();
1026                        1
1027                    }
1028                    DropType::Named => {
1029                        let count = self.named_graphs.len();
1030                        self.named_graphs.clear();
1031                        count
1032                    }
1033                    DropType::All => {
1034                        let ng = self.named_graphs.len();
1035                        self.named_graphs.clear();
1036                        self.triples.clear();
1037                        ng + 1
1038                    }
1039                };
1040                Ok(UpdateResult {
1041                    triples_inserted: 0,
1042                    triples_deleted: 0,
1043                    graphs_affected: count,
1044                })
1045            }
1046            SparqlUpdate::ClearGraph {
1047                iri,
1048                silent,
1049                clear_type,
1050            } => {
1051                let count = match clear_type {
1052                    ClearType::Graph => {
1053                        let key = iri.as_deref().unwrap_or("");
1054                        match self.named_graphs.get_mut(key) {
1055                            Some(g) => {
1056                                g.clear();
1057                                1
1058                            }
1059                            None if *silent => 0,
1060                            None => return Err(ArqError(format!("graph <{key}> does not exist"))),
1061                        }
1062                    }
1063                    ClearType::Default => {
1064                        self.triples.clear();
1065                        1
1066                    }
1067                    ClearType::Named => {
1068                        for g in self.named_graphs.values_mut() {
1069                            g.clear();
1070                        }
1071                        self.named_graphs.len()
1072                    }
1073                    ClearType::All => {
1074                        self.triples.clear();
1075                        for g in self.named_graphs.values_mut() {
1076                            g.clear();
1077                        }
1078                        self.named_graphs.len() + 1
1079                    }
1080                };
1081                Ok(UpdateResult {
1082                    triples_inserted: 0,
1083                    triples_deleted: 0,
1084                    graphs_affected: count,
1085                })
1086            }
1087            SparqlUpdate::CopyGraph {
1088                source,
1089                target,
1090                silent: _,
1091            } => {
1092                let src_triples: Vec<Triple> =
1093                    self.named_graphs.get(source).cloned().unwrap_or_default();
1094                let count = src_triples.len();
1095                let tgt = self.named_graphs.entry(target.clone()).or_default();
1096                tgt.clear();
1097                tgt.extend(src_triples);
1098                Ok(UpdateResult {
1099                    triples_inserted: count,
1100                    triples_deleted: 0,
1101                    graphs_affected: 1,
1102                })
1103            }
1104            SparqlUpdate::MoveGraph {
1105                source,
1106                target,
1107                silent: _,
1108            } => {
1109                let src_triples = self.named_graphs.remove(source).unwrap_or_default();
1110                let count = src_triples.len();
1111                let tgt = self.named_graphs.entry(target.clone()).or_default();
1112                tgt.clear();
1113                tgt.extend(src_triples);
1114                Ok(UpdateResult {
1115                    triples_inserted: count,
1116                    triples_deleted: 0,
1117                    graphs_affected: 2,
1118                })
1119            }
1120            SparqlUpdate::AddGraph {
1121                source,
1122                target,
1123                silent: _,
1124            } => {
1125                let src_triples: Vec<Triple> =
1126                    self.named_graphs.get(source).cloned().unwrap_or_default();
1127                let count = src_triples.len();
1128                let tgt = self.named_graphs.entry(target.clone()).or_default();
1129                tgt.extend(src_triples);
1130                Ok(UpdateResult {
1131                    triples_inserted: count,
1132                    triples_deleted: 0,
1133                    graphs_affected: 1,
1134                })
1135            }
1136            SparqlUpdate::Load { iri, into, silent } => {
1137                // Actual HTTP loading is not implemented in this in-memory executor.
1138                // Return success (silent) or error (non-silent).
1139                if *silent {
1140                    Ok(UpdateResult::default())
1141                } else {
1142                    Err(ArqError(format!(
1143                        "LOAD is not supported in the in-memory executor (iri=<{iri}>, into={into:?})"
1144                    )))
1145                }
1146            }
1147        }
1148    }
1149
1150    /// Execute a sequence of update operations and collect their results.
1151    pub fn execute_all(&mut self, updates: &[SparqlUpdate]) -> Result<Vec<UpdateResult>, ArqError> {
1152        updates.iter().map(|u| self.execute(u)).collect()
1153    }
1154
1155    /// Number of triples in the default graph.
1156    pub fn triple_count(&self) -> usize {
1157        self.triples.len()
1158    }
1159
1160    /// Number of named graphs (not counting the default graph).
1161    pub fn graph_count(&self) -> usize {
1162        self.named_graphs.len()
1163    }
1164
1165    /// Return the triples in a named graph, or `None` if it does not exist.
1166    pub fn get_graph(&self, iri: &str) -> Option<&Vec<Triple>> {
1167        self.named_graphs.get(iri)
1168    }
1169
1170    /// Return a reference to the default graph's triple set.
1171    pub fn default_graph(&self) -> &Vec<Triple> {
1172        &self.triples
1173    }
1174}
1175
1176impl Default for UpdateExecutor {
1177    fn default() -> Self {
1178        Self::new()
1179    }
1180}
1181
1182// ---------------------------------------------------------------------------
1183// Pattern matching helpers
1184// ---------------------------------------------------------------------------
1185
1186type Binding = HashMap<String, String>;
1187
1188/// Match a slice of triple patterns against the default graph, returning all
1189/// consistent variable bindings.  Each pattern is matched independently and
1190/// bindings from consecutive patterns are intersected by joining on shared
1191/// variable names.
1192fn match_patterns(triples: &[Triple], patterns: &[TriplePattern]) -> Vec<Binding> {
1193    let mut results: Vec<Binding> = vec![HashMap::new()];
1194
1195    for pattern in patterns {
1196        let mut next: Vec<Binding> = Vec::new();
1197        for binding in &results {
1198            for triple in triples {
1199                if let Some(new_binding) = match_pattern(triple, pattern, binding) {
1200                    next.push(new_binding);
1201                }
1202            }
1203        }
1204        results = next;
1205    }
1206
1207    results
1208}
1209
1210/// Try to extend `existing_binding` with the variable bindings produced by
1211/// matching `triple` against `pattern`.  Returns `None` on conflict.
1212fn match_pattern(triple: &Triple, pattern: &TriplePattern, existing: &Binding) -> Option<Binding> {
1213    let mut binding = existing.clone();
1214    bind_term(&triple.s, &pattern.s, &mut binding)?;
1215    bind_term(&triple.p, &pattern.p, &mut binding)?;
1216    bind_term(&triple.o, &pattern.o, &mut binding)?;
1217    Some(binding)
1218}
1219
1220/// Attempt to bind `value` against `term`, extending `binding` if `term` is a
1221/// variable.  Returns `None` when an existing binding is inconsistent.
1222fn bind_term(value: &str, term: &PatternTerm, binding: &mut Binding) -> Option<()> {
1223    match term {
1224        PatternTerm::Variable(var) => {
1225            if let Some(existing) = binding.get(var.as_str()) {
1226                if existing != value {
1227                    return None;
1228                }
1229            } else {
1230                binding.insert(var.clone(), value.to_string());
1231            }
1232            Some(())
1233        }
1234        PatternTerm::Iri(iri) => {
1235            if iri == value {
1236                Some(())
1237            } else {
1238                None
1239            }
1240        }
1241        PatternTerm::Literal(lit) => {
1242            // Compare the content without surrounding quotes.
1243            let inner = lit.trim_matches('"').trim_matches('\'');
1244            if inner == value || lit == value {
1245                Some(())
1246            } else {
1247                None
1248            }
1249        }
1250        PatternTerm::BlankNode(bn) => {
1251            if bn == value {
1252                Some(())
1253            } else {
1254                None
1255            }
1256        }
1257    }
1258}
1259
1260impl UpdateExecutor {
1261    /// Match patterns against the default graph's triple set.
1262    fn match_patterns(&self, patterns: &[TriplePattern]) -> Vec<Binding> {
1263        match_patterns(&self.triples, patterns)
1264    }
1265}
1266
1267/// Try to instantiate a single `TriplePattern` against a `Binding`, producing
1268/// a `Triple` when all positions resolve to concrete terms.
1269fn instantiate_one(pattern: &TriplePattern, binding: &Binding) -> Option<Triple> {
1270    let s = resolve_term(&pattern.s, binding)?;
1271    let p = resolve_term(&pattern.p, binding)?;
1272    let o = resolve_term(&pattern.o, binding)?;
1273    Some(Triple::new(s, p, o))
1274}
1275
1276/// Try to instantiate the first `TriplePattern` in `templates`, returning a
1277/// `Vec<Triple>` (0 or 1 elements).  This helper is used for `InsertWhere`.
1278fn instantiate_template_triple(
1279    template: Option<&TriplePattern>,
1280    binding: &Binding,
1281) -> Option<Vec<Triple>> {
1282    let tp = template?;
1283    Some(instantiate_one(tp, binding).into_iter().collect())
1284}
1285
1286/// Resolve a `PatternTerm` to a concrete string using `binding`.  Returns
1287/// `None` when a variable is unbound.
1288fn resolve_term(term: &PatternTerm, binding: &Binding) -> Option<String> {
1289    match term {
1290        PatternTerm::Variable(var) => binding.get(var.as_str()).cloned(),
1291        PatternTerm::Iri(iri) => Some(iri.clone()),
1292        PatternTerm::Literal(lit) => Some(lit.trim_matches('"').trim_matches('\'').to_string()),
1293        PatternTerm::BlankNode(bn) => Some(bn.clone()),
1294    }
1295}
1296
1297// ---------------------------------------------------------------------------
1298// Tests
1299// ---------------------------------------------------------------------------
1300
1301#[cfg(test)]
1302mod tests {
1303    use super::*;
1304
1305    // ------------------------------------------------------------------
1306    // Parser – InsertData
1307    // ------------------------------------------------------------------
1308
1309    #[test]
1310    fn test_parse_insert_data_single_triple() {
1311        let input = "INSERT DATA { <http://a> <http://b> <http://c> }";
1312        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1313        match update {
1314            SparqlUpdate::InsertData(triples) => {
1315                assert_eq!(triples.len(), 1);
1316                assert_eq!(triples[0].s, "http://a");
1317                assert_eq!(triples[0].p, "http://b");
1318                assert_eq!(triples[0].o, "http://c");
1319            }
1320            _ => panic!("wrong variant"),
1321        }
1322    }
1323
1324    #[test]
1325    fn test_parse_insert_data_multiple_triples() {
1326        let input = "INSERT DATA { <s1> <p1> <o1> . <s2> <p2> <o2> }";
1327        let result = SparqlUpdateParser::parse_one(input).expect("parse failed");
1328        if let SparqlUpdate::InsertData(triples) = result {
1329            assert_eq!(triples.len(), 2);
1330        } else {
1331            panic!("wrong variant");
1332        }
1333    }
1334
1335    // ------------------------------------------------------------------
1336    // Parser – DeleteData
1337    // ------------------------------------------------------------------
1338
1339    #[test]
1340    fn test_parse_delete_data() {
1341        let input = "DELETE DATA { <http://x> <http://y> <http://z> }";
1342        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1343        match update {
1344            SparqlUpdate::DeleteData(triples) => {
1345                assert_eq!(triples.len(), 1);
1346                assert_eq!(triples[0].s, "http://x");
1347            }
1348            _ => panic!("wrong variant"),
1349        }
1350    }
1351
1352    // ------------------------------------------------------------------
1353    // Parser – CreateGraph
1354    // ------------------------------------------------------------------
1355
1356    #[test]
1357    fn test_parse_create_graph() {
1358        let input = "CREATE GRAPH <http://example.org/g>";
1359        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1360        match update {
1361            SparqlUpdate::CreateGraph { iri, silent } => {
1362                assert_eq!(iri, "http://example.org/g");
1363                assert!(!silent);
1364            }
1365            _ => panic!("wrong variant"),
1366        }
1367    }
1368
1369    #[test]
1370    fn test_parse_create_graph_silent() {
1371        let input = "CREATE SILENT GRAPH <http://example.org/g>";
1372        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1373        match update {
1374            SparqlUpdate::CreateGraph { iri: _, silent } => {
1375                assert!(silent);
1376            }
1377            _ => panic!("wrong variant"),
1378        }
1379    }
1380
1381    // ------------------------------------------------------------------
1382    // Parser – DropGraph
1383    // ------------------------------------------------------------------
1384
1385    #[test]
1386    fn test_parse_drop_graph_named() {
1387        let input = "DROP GRAPH <http://g>";
1388        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1389        match update {
1390            SparqlUpdate::DropGraph {
1391                iri,
1392                silent,
1393                drop_type,
1394            } => {
1395                assert_eq!(iri, Some("http://g".to_string()));
1396                assert!(!silent);
1397                assert_eq!(drop_type, DropType::Graph);
1398            }
1399            _ => panic!("wrong variant"),
1400        }
1401    }
1402
1403    #[test]
1404    fn test_parse_drop_all() {
1405        let input = "DROP ALL";
1406        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1407        match update {
1408            SparqlUpdate::DropGraph { drop_type, .. } => {
1409                assert_eq!(drop_type, DropType::All);
1410            }
1411            _ => panic!("wrong variant"),
1412        }
1413    }
1414
1415    // ------------------------------------------------------------------
1416    // Parser – ClearGraph
1417    // ------------------------------------------------------------------
1418
1419    #[test]
1420    fn test_parse_clear_default() {
1421        let input = "CLEAR DEFAULT";
1422        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1423        match update {
1424            SparqlUpdate::ClearGraph { clear_type, .. } => {
1425                assert_eq!(clear_type, ClearType::Default);
1426            }
1427            _ => panic!("wrong variant"),
1428        }
1429    }
1430
1431    // ------------------------------------------------------------------
1432    // Parser – CopyGraph, MoveGraph, AddGraph
1433    // ------------------------------------------------------------------
1434
1435    #[test]
1436    fn test_parse_copy_graph() {
1437        let input = "COPY <http://src> TO <http://dst>";
1438        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1439        match update {
1440            SparqlUpdate::CopyGraph {
1441                source,
1442                target,
1443                silent,
1444            } => {
1445                assert_eq!(source, "http://src");
1446                assert_eq!(target, "http://dst");
1447                assert!(!silent);
1448            }
1449            _ => panic!("wrong variant"),
1450        }
1451    }
1452
1453    #[test]
1454    fn test_parse_move_graph() {
1455        let input = "MOVE <http://old> TO <http://new>";
1456        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1457        match update {
1458            SparqlUpdate::MoveGraph { source, target, .. } => {
1459                assert_eq!(source, "http://old");
1460                assert_eq!(target, "http://new");
1461            }
1462            _ => panic!("wrong variant"),
1463        }
1464    }
1465
1466    #[test]
1467    fn test_parse_add_graph() {
1468        let input = "ADD <http://src> TO <http://dst>";
1469        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1470        match update {
1471            SparqlUpdate::AddGraph { .. } => {}
1472            _ => panic!("wrong variant"),
1473        }
1474    }
1475
1476    // ------------------------------------------------------------------
1477    // Parser – LOAD
1478    // ------------------------------------------------------------------
1479
1480    #[test]
1481    fn test_parse_load_basic() {
1482        let input = "LOAD <http://data.example.org/data.ttl>";
1483        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1484        match update {
1485            SparqlUpdate::Load { iri, into, silent } => {
1486                assert_eq!(iri, "http://data.example.org/data.ttl");
1487                assert!(into.is_none());
1488                assert!(!silent);
1489            }
1490            _ => panic!("wrong variant"),
1491        }
1492    }
1493
1494    #[test]
1495    fn test_parse_load_into_graph() {
1496        let input = "LOAD <http://src.ttl> INTO GRAPH <http://target>";
1497        let update = SparqlUpdateParser::parse_one(input).expect("parse failed");
1498        match update {
1499            SparqlUpdate::Load { into, .. } => {
1500                assert_eq!(into, Some("http://target".to_string()));
1501            }
1502            _ => panic!("wrong variant"),
1503        }
1504    }
1505
1506    // ------------------------------------------------------------------
1507    // Parser – multiple operations
1508    // ------------------------------------------------------------------
1509
1510    #[test]
1511    fn test_parse_multiple_operations() {
1512        let input = "INSERT DATA { <s1> <p1> <o1> } ; DELETE DATA { <s2> <p2> <o2> }";
1513        let updates = SparqlUpdateParser::parse(input).expect("parse failed");
1514        assert_eq!(updates.len(), 2);
1515    }
1516
1517    #[test]
1518    fn test_parse_empty_input() {
1519        let updates = SparqlUpdateParser::parse("").expect("parse failed");
1520        assert!(updates.is_empty());
1521    }
1522
1523    #[test]
1524    fn test_parse_unknown_keyword_returns_error() {
1525        let result = SparqlUpdateParser::parse_one("FROBULATE { }");
1526        assert!(result.is_err());
1527    }
1528
1529    // ------------------------------------------------------------------
1530    // UpdateExecutor – InsertData / DeleteData
1531    // ------------------------------------------------------------------
1532
1533    #[test]
1534    fn test_executor_insert_data() {
1535        let mut exec = UpdateExecutor::new();
1536        let update = SparqlUpdate::InsertData(vec![Triple::new("s", "p", "o")]);
1537        let result = exec.execute(&update).expect("execute failed");
1538        assert_eq!(result.triples_inserted, 1);
1539        assert_eq!(exec.triple_count(), 1);
1540    }
1541
1542    #[test]
1543    fn test_executor_delete_data_existing() {
1544        let mut exec = UpdateExecutor::new();
1545        let t = Triple::new("s", "p", "o");
1546        exec.execute(&SparqlUpdate::InsertData(vec![t.clone()]))
1547            .expect("insert failed");
1548        let result = exec
1549            .execute(&SparqlUpdate::DeleteData(vec![t]))
1550            .expect("delete failed");
1551        assert_eq!(result.triples_deleted, 1);
1552        assert_eq!(exec.triple_count(), 0);
1553    }
1554
1555    #[test]
1556    fn test_executor_delete_data_nonexistent() {
1557        let mut exec = UpdateExecutor::new();
1558        let result = exec
1559            .execute(&SparqlUpdate::DeleteData(vec![Triple::new("x", "y", "z")]))
1560            .expect("delete failed");
1561        assert_eq!(result.triples_deleted, 0);
1562    }
1563
1564    // ------------------------------------------------------------------
1565    // UpdateExecutor – CreateGraph / DropGraph
1566    // ------------------------------------------------------------------
1567
1568    #[test]
1569    fn test_executor_create_graph() {
1570        let mut exec = UpdateExecutor::new();
1571        exec.execute(&SparqlUpdate::CreateGraph {
1572            iri: "http://g".to_string(),
1573            silent: false,
1574        })
1575        .expect("create failed");
1576        assert_eq!(exec.graph_count(), 1);
1577        assert!(exec.get_graph("http://g").is_some());
1578    }
1579
1580    #[test]
1581    fn test_executor_create_duplicate_graph_silent() {
1582        let mut exec = UpdateExecutor::new();
1583        let update = SparqlUpdate::CreateGraph {
1584            iri: "http://g".to_string(),
1585            silent: true,
1586        };
1587        exec.execute(&update).expect("first create failed");
1588        exec.execute(&update)
1589            .expect("second create (silent) should not error");
1590    }
1591
1592    #[test]
1593    fn test_executor_create_duplicate_graph_non_silent_errors() {
1594        let mut exec = UpdateExecutor::new();
1595        let update = SparqlUpdate::CreateGraph {
1596            iri: "http://g".to_string(),
1597            silent: false,
1598        };
1599        exec.execute(&update).expect("first create failed");
1600        let result = exec.execute(&update);
1601        assert!(result.is_err());
1602    }
1603
1604    #[test]
1605    fn test_executor_drop_named_graph() {
1606        let mut exec = UpdateExecutor::new();
1607        exec.execute(&SparqlUpdate::CreateGraph {
1608            iri: "http://g".to_string(),
1609            silent: false,
1610        })
1611        .expect("create failed");
1612        exec.execute(&SparqlUpdate::DropGraph {
1613            iri: Some("http://g".to_string()),
1614            silent: false,
1615            drop_type: DropType::Graph,
1616        })
1617        .expect("drop failed");
1618        assert_eq!(exec.graph_count(), 0);
1619    }
1620
1621    // ------------------------------------------------------------------
1622    // UpdateExecutor – ClearGraph
1623    // ------------------------------------------------------------------
1624
1625    #[test]
1626    fn test_executor_clear_default_graph() {
1627        let mut exec = UpdateExecutor::new();
1628        exec.execute(&SparqlUpdate::InsertData(vec![
1629            Triple::new("s1", "p1", "o1"),
1630            Triple::new("s2", "p2", "o2"),
1631        ]))
1632        .expect("insert failed");
1633        exec.execute(&SparqlUpdate::ClearGraph {
1634            iri: None,
1635            silent: false,
1636            clear_type: ClearType::Default,
1637        })
1638        .expect("clear failed");
1639        assert_eq!(exec.triple_count(), 0);
1640    }
1641
1642    // ------------------------------------------------------------------
1643    // UpdateExecutor – execute_all
1644    // ------------------------------------------------------------------
1645
1646    #[test]
1647    fn test_executor_execute_all() {
1648        let mut exec = UpdateExecutor::new();
1649        let updates = vec![
1650            SparqlUpdate::InsertData(vec![Triple::new("a", "b", "c")]),
1651            SparqlUpdate::InsertData(vec![Triple::new("d", "e", "f")]),
1652        ];
1653        let results = exec.execute_all(&updates).expect("execute_all failed");
1654        assert_eq!(results.len(), 2);
1655        assert_eq!(exec.triple_count(), 2);
1656    }
1657
1658    // ------------------------------------------------------------------
1659    // Triple / PatternTerm helpers
1660    // ------------------------------------------------------------------
1661
1662    #[test]
1663    fn test_triple_equality() {
1664        let t1 = Triple::new("s", "p", "o");
1665        let t2 = Triple::new("s", "p", "o");
1666        assert_eq!(t1, t2);
1667    }
1668
1669    #[test]
1670    fn test_pattern_term_is_variable() {
1671        let var = PatternTerm::Variable("x".to_string());
1672        assert!(var.is_variable());
1673        let iri = PatternTerm::Iri("http://a".to_string());
1674        assert!(!iri.is_variable());
1675    }
1676
1677    #[test]
1678    fn test_pattern_term_variable_name() {
1679        let var = PatternTerm::Variable("myVar".to_string());
1680        assert_eq!(var.variable_name(), Some("myVar"));
1681        let iri = PatternTerm::Iri("http://a".to_string());
1682        assert_eq!(iri.variable_name(), None);
1683    }
1684
1685    // ------------------------------------------------------------------
1686    // UpdateExecutor – CopyGraph / MoveGraph / AddGraph
1687    // ------------------------------------------------------------------
1688
1689    #[test]
1690    fn test_executor_copy_graph() {
1691        let mut exec = UpdateExecutor::new();
1692        exec.execute(&SparqlUpdate::CreateGraph {
1693            iri: "http://src".into(),
1694            silent: false,
1695        })
1696        .expect("create src");
1697        // Manually add a triple to the named graph via the HashMap.
1698        exec.named_graphs
1699            .get_mut("http://src")
1700            .expect("src exists")
1701            .push(Triple::new("a", "b", "c"));
1702
1703        exec.execute(&SparqlUpdate::CopyGraph {
1704            source: "http://src".into(),
1705            target: "http://dst".into(),
1706            silent: false,
1707        })
1708        .expect("copy failed");
1709        assert_eq!(exec.get_graph("http://dst").expect("dst exists").len(), 1);
1710    }
1711
1712    #[test]
1713    fn test_executor_move_graph() {
1714        let mut exec = UpdateExecutor::new();
1715        exec.execute(&SparqlUpdate::CreateGraph {
1716            iri: "http://src".into(),
1717            silent: false,
1718        })
1719        .expect("create src");
1720        exec.named_graphs
1721            .get_mut("http://src")
1722            .expect("src exists")
1723            .push(Triple::new("x", "y", "z"));
1724
1725        exec.execute(&SparqlUpdate::MoveGraph {
1726            source: "http://src".into(),
1727            target: "http://dst".into(),
1728            silent: false,
1729        })
1730        .expect("move failed");
1731
1732        assert!(exec.get_graph("http://src").is_none());
1733        assert_eq!(exec.get_graph("http://dst").expect("dst exists").len(), 1);
1734    }
1735}