Skip to main content

oxirs_arq/
update_graph_management.rs

1//! SPARQL 1.1 UPDATE Graph Management Operations
2//!
3//! This module implements the full set of SPARQL 1.1 UPDATE graph management
4//! operations as specified at:
5//! <https://www.w3.org/TR/sparql11-update/#graphManagement>
6//!
7//! Supported operations:
8//! - `LOAD <iri> [SILENT] [INTO GRAPH <g>]`
9//! - `CLEAR [SILENT] (DEFAULT | NAMED | ALL | GRAPH <g>)`
10//! - `DROP [SILENT] (DEFAULT | NAMED | ALL | GRAPH <g>)`
11//! - `CREATE [SILENT] GRAPH <g>`
12//! - `COPY [SILENT] <source> TO <dest>`
13//! - `MOVE [SILENT] <source> TO <dest>`
14//! - `ADD [SILENT] <source> TO <dest>`
15
16use anyhow::{anyhow, Result};
17use std::collections::HashMap;
18
19// ---------------------------------------------------------------------------
20// Core data types
21// ---------------------------------------------------------------------------
22
23/// A single RDF triple (subject, predicate, object all as plain strings / IRIs).
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25pub struct Triple {
26    pub subject: String,
27    pub predicate: String,
28    pub object: String,
29}
30
31impl Triple {
32    /// Create a new triple.
33    pub fn new(
34        subject: impl Into<String>,
35        predicate: impl Into<String>,
36        object: impl Into<String>,
37    ) -> Self {
38        Self {
39            subject: subject.into(),
40            predicate: predicate.into(),
41            object: object.into(),
42        }
43    }
44}
45
46// ---------------------------------------------------------------------------
47// Graph target
48// ---------------------------------------------------------------------------
49
50/// The target for graph management operations (mirrors the SPARQL 1.1 grammar).
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum GraphManagementTarget {
53    /// The unnamed default graph.
54    Default,
55    /// A specific named graph identified by its IRI.
56    Named(String),
57    /// All named graphs (does **not** include the default graph unless combined
58    /// with `All`).
59    AllNamed,
60    /// All graphs: default graph plus every named graph.
61    All,
62}
63
64// ---------------------------------------------------------------------------
65// Operations
66// ---------------------------------------------------------------------------
67
68/// A SPARQL 1.1 UPDATE graph management operation.
69#[derive(Debug, Clone, PartialEq)]
70pub enum GraphManagementOp {
71    /// `LOAD <iri> [SILENT] [INTO GRAPH <g>]`
72    Load {
73        /// The IRI of the remote document to load.
74        iri: String,
75        /// Destination graph; `None` means the default graph.
76        into_graph: Option<String>,
77        /// If `true`, errors are suppressed.
78        silent: bool,
79    },
80
81    /// `CLEAR [SILENT] (DEFAULT | NAMED | ALL | GRAPH <g>)`
82    Clear {
83        /// Which graph(s) to empty.
84        target: GraphManagementTarget,
85        /// If `true`, errors are suppressed.
86        silent: bool,
87    },
88
89    /// `DROP [SILENT] (DEFAULT | NAMED | ALL | GRAPH <g>)`
90    Drop {
91        /// Which graph(s) to drop entirely.
92        target: GraphManagementTarget,
93        /// If `true`, errors (e.g. non-existent graph) are suppressed.
94        silent: bool,
95    },
96
97    /// `CREATE [SILENT] GRAPH <g>`
98    Create {
99        /// IRI of the graph to create.
100        graph: String,
101        /// If `true`, errors (e.g. graph already exists) are suppressed.
102        silent: bool,
103    },
104
105    /// `COPY [SILENT] <source> TO <dest>`
106    ///
107    /// The destination graph is first cleared, then all triples from the source
108    /// are copied into it.  The source graph is left unchanged.
109    Copy {
110        /// Source graph.
111        source: GraphManagementTarget,
112        /// Destination graph.
113        destination: GraphManagementTarget,
114        /// If `true`, errors are suppressed.
115        silent: bool,
116    },
117
118    /// `MOVE [SILENT] <source> TO <dest>`
119    ///
120    /// Like `COPY`, but the source graph is dropped after the copy.
121    Move {
122        /// Source graph.
123        source: GraphManagementTarget,
124        /// Destination graph.
125        destination: GraphManagementTarget,
126        /// If `true`, errors are suppressed.
127        silent: bool,
128    },
129
130    /// `ADD [SILENT] <source> TO <dest>`
131    ///
132    /// Adds (merges) all triples from the source graph into the destination
133    /// graph without clearing the destination first.
134    Add {
135        /// Source graph.
136        source: GraphManagementTarget,
137        /// Destination graph.
138        destination: GraphManagementTarget,
139        /// If `true`, errors are suppressed.
140        silent: bool,
141    },
142}
143
144// ---------------------------------------------------------------------------
145// Result type
146// ---------------------------------------------------------------------------
147
148/// Result returned by [`GraphManagementExecutor::execute`].
149#[derive(Debug, Clone, Default)]
150pub struct GraphManagementResult {
151    /// Number of triples that were inserted or copied.
152    pub triples_affected: usize,
153    /// IRIs (or `"DEFAULT"`) of graphs that were created, cleared, dropped or
154    /// otherwise affected by the operation.
155    pub graphs_affected: Vec<String>,
156}
157
158// ---------------------------------------------------------------------------
159// In-memory dataset
160// ---------------------------------------------------------------------------
161
162/// A lightweight in-memory RDF dataset used for graph management operations.
163///
164/// It maintains a *default graph* (unnamed) and any number of *named graphs*.
165/// All graphs are identified by their IRI strings.
166#[derive(Debug, Clone, Default)]
167pub struct GraphManagementDataset {
168    /// Triples in the unnamed default graph.
169    pub default_graph: Vec<Triple>,
170    /// Named graphs, keyed by their IRI string.
171    pub named_graphs: HashMap<String, Vec<Triple>>,
172}
173
174impl GraphManagementDataset {
175    /// Create an empty dataset.
176    pub fn new() -> Self {
177        Self::default()
178    }
179
180    /// Add a triple to a graph.
181    ///
182    /// `graph` is `None` for the default graph, or `Some(iri)` for a named
183    /// graph.  If the named graph does not yet exist it is created implicitly.
184    pub fn add_triple(&mut self, graph: Option<&str>, triple: Triple) {
185        match graph {
186            None => self.default_graph.push(triple),
187            Some(iri) => {
188                self.named_graphs
189                    .entry(iri.to_owned())
190                    .or_default()
191                    .push(triple);
192            }
193        }
194    }
195
196    /// Return a slice of the triples in the given graph.
197    ///
198    /// Returns an empty slice for graphs that do not exist.
199    pub fn get_graph(&self, graph: Option<&str>) -> &[Triple] {
200        match graph {
201            None => &self.default_graph,
202            Some(iri) => self.named_graphs.get(iri).map(Vec::as_slice).unwrap_or(&[]),
203        }
204    }
205
206    /// Return the IRI strings of every named graph in the dataset.
207    pub fn graph_names(&self) -> Vec<String> {
208        self.named_graphs.keys().cloned().collect()
209    }
210
211    /// Return the triple count for the given graph.
212    pub fn triple_count(&self, graph: Option<&str>) -> usize {
213        self.get_graph(graph).len()
214    }
215
216    /// Check whether a named graph with the given IRI exists.
217    pub fn named_graph_exists(&self, iri: &str) -> bool {
218        self.named_graphs.contains_key(iri)
219    }
220}
221
222// ---------------------------------------------------------------------------
223// Executor
224// ---------------------------------------------------------------------------
225
226/// Executor for SPARQL 1.1 UPDATE graph management operations.
227///
228/// All operations are applied to a [`GraphManagementDataset`] in memory.
229/// HTTP-based `LOAD` is intentionally not implemented (see
230/// [`Self::execute`] for details).
231pub struct GraphManagementExecutor;
232
233impl GraphManagementExecutor {
234    /// Execute a graph management operation against the supplied dataset.
235    ///
236    /// ### Notes on `LOAD`
237    /// `LOAD` requires an HTTP (or file-system) client and is therefore **not**
238    /// implemented here.  Calling `LOAD` with `silent = false` returns an
239    /// error; with `silent = true` it returns a no-op success.  Real
240    /// implementations should use `reqwest` or `ureq` to fetch the document.
241    pub fn execute(
242        op: &GraphManagementOp,
243        dataset: &mut GraphManagementDataset,
244    ) -> Result<GraphManagementResult> {
245        match op {
246            GraphManagementOp::Load {
247                iri,
248                into_graph,
249                silent,
250            } => Self::execute_load(iri, into_graph.as_deref(), *silent),
251            GraphManagementOp::Clear { target, silent } => {
252                Self::execute_clear(target, *silent, dataset)
253            }
254            GraphManagementOp::Drop { target, silent } => {
255                Self::execute_drop(target, *silent, dataset)
256            }
257            GraphManagementOp::Create { graph, silent } => {
258                Self::execute_create(graph, *silent, dataset)
259            }
260            GraphManagementOp::Copy {
261                source,
262                destination,
263                silent,
264            } => Self::execute_copy(source, destination, *silent, dataset),
265            GraphManagementOp::Move {
266                source,
267                destination,
268                silent,
269            } => Self::execute_move(source, destination, *silent, dataset),
270            GraphManagementOp::Add {
271                source,
272                destination,
273                silent,
274            } => Self::execute_add(source, destination, *silent, dataset),
275        }
276    }
277
278    // -----------------------------------------------------------------------
279    // LOAD
280    // -----------------------------------------------------------------------
281
282    fn execute_load(
283        iri: &str,
284        _into_graph: Option<&str>,
285        silent: bool,
286    ) -> Result<GraphManagementResult> {
287        // HTTP loading is not implemented in this in-memory executor.
288        if silent {
289            Ok(GraphManagementResult::default())
290        } else {
291            Err(anyhow!(
292                "LOAD <{iri}> is not supported by the in-memory graph management executor. \
293                 Use a network-capable executor or specify SILENT to suppress this error."
294            ))
295        }
296    }
297
298    // -----------------------------------------------------------------------
299    // CLEAR
300    // -----------------------------------------------------------------------
301
302    fn execute_clear(
303        target: &GraphManagementTarget,
304        silent: bool,
305        dataset: &mut GraphManagementDataset,
306    ) -> Result<GraphManagementResult> {
307        let mut result = GraphManagementResult::default();
308
309        match target {
310            GraphManagementTarget::Default => {
311                result.triples_affected = dataset.default_graph.len();
312                dataset.default_graph.clear();
313                result.graphs_affected.push("DEFAULT".to_owned());
314            }
315            GraphManagementTarget::Named(iri) => match dataset.named_graphs.get_mut(iri) {
316                Some(triples) => {
317                    result.triples_affected = triples.len();
318                    triples.clear();
319                    result.graphs_affected.push(iri.clone());
320                }
321                None => {
322                    if !silent {
323                        return Err(anyhow!("CLEAR GRAPH <{iri}>: named graph does not exist"));
324                    }
325                }
326            },
327            GraphManagementTarget::AllNamed => {
328                for (iri, triples) in &mut dataset.named_graphs {
329                    result.triples_affected += triples.len();
330                    triples.clear();
331                    result.graphs_affected.push(iri.clone());
332                }
333            }
334            GraphManagementTarget::All => {
335                result.triples_affected += dataset.default_graph.len();
336                dataset.default_graph.clear();
337                result.graphs_affected.push("DEFAULT".to_owned());
338
339                for (iri, triples) in &mut dataset.named_graphs {
340                    result.triples_affected += triples.len();
341                    triples.clear();
342                    result.graphs_affected.push(iri.clone());
343                }
344            }
345        }
346
347        Ok(result)
348    }
349
350    // -----------------------------------------------------------------------
351    // DROP
352    // -----------------------------------------------------------------------
353
354    fn execute_drop(
355        target: &GraphManagementTarget,
356        silent: bool,
357        dataset: &mut GraphManagementDataset,
358    ) -> Result<GraphManagementResult> {
359        let mut result = GraphManagementResult::default();
360
361        match target {
362            GraphManagementTarget::Default => {
363                result.triples_affected = dataset.default_graph.len();
364                dataset.default_graph.clear();
365                result.graphs_affected.push("DEFAULT".to_owned());
366            }
367            GraphManagementTarget::Named(iri) => match dataset.named_graphs.remove(iri) {
368                Some(triples) => {
369                    result.triples_affected = triples.len();
370                    result.graphs_affected.push(iri.clone());
371                }
372                None => {
373                    if !silent {
374                        return Err(anyhow!("DROP GRAPH <{iri}>: named graph does not exist"));
375                    }
376                }
377            },
378            GraphManagementTarget::AllNamed => {
379                for (iri, triples) in dataset.named_graphs.drain() {
380                    result.triples_affected += triples.len();
381                    result.graphs_affected.push(iri);
382                }
383            }
384            GraphManagementTarget::All => {
385                result.triples_affected += dataset.default_graph.len();
386                dataset.default_graph.clear();
387                result.graphs_affected.push("DEFAULT".to_owned());
388
389                for (iri, triples) in dataset.named_graphs.drain() {
390                    result.triples_affected += triples.len();
391                    result.graphs_affected.push(iri);
392                }
393            }
394        }
395
396        Ok(result)
397    }
398
399    // -----------------------------------------------------------------------
400    // CREATE
401    // -----------------------------------------------------------------------
402
403    fn execute_create(
404        graph: &str,
405        silent: bool,
406        dataset: &mut GraphManagementDataset,
407    ) -> Result<GraphManagementResult> {
408        if dataset.named_graphs.contains_key(graph) {
409            if !silent {
410                return Err(anyhow!("CREATE GRAPH <{graph}>: graph already exists"));
411            }
412            return Ok(GraphManagementResult::default());
413        }
414
415        dataset.named_graphs.insert(graph.to_owned(), Vec::new());
416
417        Ok(GraphManagementResult {
418            triples_affected: 0,
419            graphs_affected: vec![graph.to_owned()],
420        })
421    }
422
423    // -----------------------------------------------------------------------
424    // Helpers for resolving graph contents
425    // -----------------------------------------------------------------------
426
427    /// Retrieve a *clone* of all triples in the given target, validating that
428    /// it exists when `silent` is false.
429    fn get_triples_for_target(
430        target: &GraphManagementTarget,
431        silent: bool,
432        dataset: &GraphManagementDataset,
433    ) -> Result<Vec<Triple>> {
434        match target {
435            GraphManagementTarget::Default => Ok(dataset.default_graph.clone()),
436            GraphManagementTarget::Named(iri) => match dataset.named_graphs.get(iri) {
437                Some(triples) => Ok(triples.clone()),
438                None => {
439                    if silent {
440                        Ok(vec![])
441                    } else {
442                        Err(anyhow!("Graph <{iri}> does not exist"))
443                    }
444                }
445            },
446            // COPY/MOVE/ADD with AllNamed or All as source is a SPARQL error
447            // (the spec requires a single graph as source).
448            GraphManagementTarget::AllNamed | GraphManagementTarget::All => {
449                if silent {
450                    Ok(vec![])
451                } else {
452                    Err(anyhow!(
453                        "COPY/MOVE/ADD source must be a single graph (DEFAULT or a named graph IRI), \
454                         not ALL or NAMED"
455                    ))
456                }
457            }
458        }
459    }
460
461    /// Clear the destination graph storage and return the mutable reference.
462    fn clear_destination(
463        target: &GraphManagementTarget,
464        dataset: &mut GraphManagementDataset,
465    ) -> Result<()> {
466        match target {
467            GraphManagementTarget::Default => {
468                dataset.default_graph.clear();
469            }
470            GraphManagementTarget::Named(iri) => {
471                dataset.named_graphs.entry(iri.clone()).or_default().clear();
472            }
473            GraphManagementTarget::AllNamed | GraphManagementTarget::All => {
474                return Err(anyhow!(
475                    "Destination must be a single graph (DEFAULT or a named graph IRI)"
476                ));
477            }
478        }
479        Ok(())
480    }
481
482    /// Write triples into the destination graph (appending).
483    fn write_triples_to_destination(
484        target: &GraphManagementTarget,
485        triples: Vec<Triple>,
486        dataset: &mut GraphManagementDataset,
487    ) -> Result<usize> {
488        let count = triples.len();
489        match target {
490            GraphManagementTarget::Default => {
491                dataset.default_graph.extend(triples);
492            }
493            GraphManagementTarget::Named(iri) => {
494                dataset
495                    .named_graphs
496                    .entry(iri.clone())
497                    .or_default()
498                    .extend(triples);
499            }
500            GraphManagementTarget::AllNamed | GraphManagementTarget::All => {
501                return Err(anyhow!(
502                    "Destination must be a single graph (DEFAULT or a named graph IRI)"
503                ));
504            }
505        }
506        Ok(count)
507    }
508
509    /// Remove the source graph from the dataset (used by MOVE).
510    fn drop_source(target: &GraphManagementTarget, dataset: &mut GraphManagementDataset) {
511        match target {
512            GraphManagementTarget::Default => {
513                dataset.default_graph.clear();
514            }
515            GraphManagementTarget::Named(iri) => {
516                dataset.named_graphs.remove(iri);
517            }
518            // These variants are caught earlier; safe to no-op here.
519            GraphManagementTarget::AllNamed | GraphManagementTarget::All => {}
520        }
521    }
522
523    /// Return the canonical string label for a target (for reporting).
524    fn target_label(target: &GraphManagementTarget) -> String {
525        match target {
526            GraphManagementTarget::Default => "DEFAULT".to_owned(),
527            GraphManagementTarget::Named(iri) => iri.clone(),
528            GraphManagementTarget::AllNamed => "NAMED".to_owned(),
529            GraphManagementTarget::All => "ALL".to_owned(),
530        }
531    }
532
533    // -----------------------------------------------------------------------
534    // COPY
535    // -----------------------------------------------------------------------
536
537    fn execute_copy(
538        source: &GraphManagementTarget,
539        dest: &GraphManagementTarget,
540        silent: bool,
541        dataset: &mut GraphManagementDataset,
542    ) -> Result<GraphManagementResult> {
543        // If source == destination this is a no-op per the W3C spec.
544        if source == dest {
545            return Ok(GraphManagementResult {
546                triples_affected: 0,
547                graphs_affected: vec![Self::target_label(dest)],
548            });
549        }
550
551        let source_triples = Self::get_triples_for_target(source, silent, dataset)?;
552        let count = source_triples.len();
553
554        Self::clear_destination(dest, dataset)?;
555        Self::write_triples_to_destination(dest, source_triples, dataset)?;
556
557        Ok(GraphManagementResult {
558            triples_affected: count,
559            graphs_affected: vec![Self::target_label(source), Self::target_label(dest)],
560        })
561    }
562
563    // -----------------------------------------------------------------------
564    // MOVE
565    // -----------------------------------------------------------------------
566
567    fn execute_move(
568        source: &GraphManagementTarget,
569        dest: &GraphManagementTarget,
570        silent: bool,
571        dataset: &mut GraphManagementDataset,
572    ) -> Result<GraphManagementResult> {
573        // MOVE to self is a no-op.
574        if source == dest {
575            return Ok(GraphManagementResult {
576                triples_affected: 0,
577                graphs_affected: vec![Self::target_label(dest)],
578            });
579        }
580
581        let source_triples = Self::get_triples_for_target(source, silent, dataset)?;
582        let count = source_triples.len();
583
584        Self::clear_destination(dest, dataset)?;
585        Self::write_triples_to_destination(dest, source_triples, dataset)?;
586        Self::drop_source(source, dataset);
587
588        Ok(GraphManagementResult {
589            triples_affected: count,
590            graphs_affected: vec![Self::target_label(source), Self::target_label(dest)],
591        })
592    }
593
594    // -----------------------------------------------------------------------
595    // ADD
596    // -----------------------------------------------------------------------
597
598    fn execute_add(
599        source: &GraphManagementTarget,
600        dest: &GraphManagementTarget,
601        silent: bool,
602        dataset: &mut GraphManagementDataset,
603    ) -> Result<GraphManagementResult> {
604        // ADD to self is a no-op.
605        if source == dest {
606            return Ok(GraphManagementResult {
607                triples_affected: 0,
608                graphs_affected: vec![Self::target_label(dest)],
609            });
610        }
611
612        let source_triples = Self::get_triples_for_target(source, silent, dataset)?;
613        let count = source_triples.len();
614
615        Self::write_triples_to_destination(dest, source_triples, dataset)?;
616
617        Ok(GraphManagementResult {
618            triples_affected: count,
619            graphs_affected: vec![Self::target_label(source), Self::target_label(dest)],
620        })
621    }
622}
623
624// ---------------------------------------------------------------------------
625// Tests
626// ---------------------------------------------------------------------------
627
628#[cfg(test)]
629mod tests {
630    use super::*;
631
632    // -----------------------------------------------------------------------
633    // Helper builders
634    // -----------------------------------------------------------------------
635
636    fn triple(s: &str, p: &str, o: &str) -> Triple {
637        Triple::new(s, p, o)
638    }
639
640    fn ex(local: &str) -> String {
641        format!("http://example.org/{local}")
642    }
643
644    fn g1() -> String {
645        ex("g1")
646    }
647    fn g2() -> String {
648        ex("g2")
649    }
650    fn g3() -> String {
651        ex("g3")
652    }
653
654    fn t1() -> Triple {
655        triple(&ex("s1"), &ex("p1"), &ex("o1"))
656    }
657    fn t2() -> Triple {
658        triple(&ex("s2"), &ex("p2"), &ex("o2"))
659    }
660    fn t3() -> Triple {
661        triple(&ex("s3"), &ex("p3"), &ex("o3"))
662    }
663
664    fn dataset_with_default_triples(triples: &[Triple]) -> GraphManagementDataset {
665        let mut ds = GraphManagementDataset::new();
666        for t in triples {
667            ds.add_triple(None, t.clone());
668        }
669        ds
670    }
671
672    fn dataset_with_named_triples(iri: &str, triples: &[Triple]) -> GraphManagementDataset {
673        let mut ds = GraphManagementDataset::new();
674        for t in triples {
675            ds.add_triple(Some(iri), t.clone());
676        }
677        ds
678    }
679
680    // -----------------------------------------------------------------------
681    // Triple / dataset basics
682    // -----------------------------------------------------------------------
683
684    #[test]
685    fn test_triple_new() {
686        let t = triple("s", "p", "o");
687        assert_eq!(t.subject, "s");
688        assert_eq!(t.predicate, "p");
689        assert_eq!(t.object, "o");
690    }
691
692    #[test]
693    fn test_triple_equality() {
694        let a = triple("s", "p", "o");
695        let b = triple("s", "p", "o");
696        let c = triple("x", "p", "o");
697        assert_eq!(a, b);
698        assert_ne!(a, c);
699    }
700
701    #[test]
702    fn test_dataset_new_is_empty() {
703        let ds = GraphManagementDataset::new();
704        assert_eq!(ds.triple_count(None), 0);
705        assert!(ds.graph_names().is_empty());
706    }
707
708    #[test]
709    fn test_dataset_add_triple_default_graph() {
710        let mut ds = GraphManagementDataset::new();
711        ds.add_triple(None, t1());
712        assert_eq!(ds.triple_count(None), 1);
713    }
714
715    #[test]
716    fn test_dataset_add_triple_named_graph() {
717        let mut ds = GraphManagementDataset::new();
718        ds.add_triple(Some(&g1()), t1());
719        assert_eq!(ds.triple_count(Some(&g1())), 1);
720        assert!(ds.named_graph_exists(&g1()));
721    }
722
723    #[test]
724    fn test_dataset_add_triple_creates_named_graph() {
725        let mut ds = GraphManagementDataset::new();
726        assert!(!ds.named_graph_exists(&g1()));
727        ds.add_triple(Some(&g1()), t1());
728        assert!(ds.named_graph_exists(&g1()));
729    }
730
731    #[test]
732    fn test_dataset_get_graph_default_empty() {
733        let ds = GraphManagementDataset::new();
734        assert_eq!(ds.get_graph(None).len(), 0);
735    }
736
737    #[test]
738    fn test_dataset_get_graph_nonexistent_named_returns_empty() {
739        let ds = GraphManagementDataset::new();
740        assert_eq!(ds.get_graph(Some("http://no-such-graph")).len(), 0);
741    }
742
743    #[test]
744    fn test_dataset_graph_names() {
745        let mut ds = GraphManagementDataset::new();
746        ds.add_triple(Some(&g1()), t1());
747        ds.add_triple(Some(&g2()), t2());
748        let mut names = ds.graph_names();
749        names.sort();
750        assert_eq!(names.len(), 2);
751    }
752
753    #[test]
754    fn test_dataset_triple_count_multiple() {
755        let mut ds = GraphManagementDataset::new();
756        ds.add_triple(None, t1());
757        ds.add_triple(None, t2());
758        ds.add_triple(Some(&g1()), t3());
759        assert_eq!(ds.triple_count(None), 2);
760        assert_eq!(ds.triple_count(Some(&g1())), 1);
761    }
762
763    // -----------------------------------------------------------------------
764    // CLEAR tests
765    // -----------------------------------------------------------------------
766
767    #[test]
768    fn test_clear_default_removes_all_triples() {
769        let mut ds = dataset_with_default_triples(&[t1(), t2(), t3()]);
770        let op = GraphManagementOp::Clear {
771            target: GraphManagementTarget::Default,
772            silent: false,
773        };
774        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
775        assert_eq!(ds.triple_count(None), 0);
776        assert_eq!(result.triples_affected, 3);
777        assert!(result.graphs_affected.contains(&"DEFAULT".to_owned()));
778    }
779
780    #[test]
781    fn test_clear_named_graph_empties_it() {
782        let mut ds = dataset_with_named_triples(&g1(), &[t1(), t2()]);
783        let op = GraphManagementOp::Clear {
784            target: GraphManagementTarget::Named(g1()),
785            silent: false,
786        };
787        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
788        assert_eq!(ds.triple_count(Some(&g1())), 0);
789        // The graph itself still exists (CLEAR does not drop)
790        assert!(ds.named_graph_exists(&g1()));
791    }
792
793    #[test]
794    fn test_clear_named_keeps_other_graphs_intact() {
795        let mut ds = GraphManagementDataset::new();
796        ds.add_triple(Some(&g1()), t1());
797        ds.add_triple(Some(&g2()), t2());
798        let op = GraphManagementOp::Clear {
799            target: GraphManagementTarget::Named(g1()),
800            silent: false,
801        };
802        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
803        assert_eq!(ds.triple_count(Some(&g1())), 0);
804        assert_eq!(ds.triple_count(Some(&g2())), 1);
805    }
806
807    #[test]
808    fn test_clear_named_nonexistent_without_silent_errors() {
809        let mut ds = GraphManagementDataset::new();
810        let op = GraphManagementOp::Clear {
811            target: GraphManagementTarget::Named(g1()),
812            silent: false,
813        };
814        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_err());
815    }
816
817    #[test]
818    fn test_clear_named_nonexistent_with_silent_succeeds() {
819        let mut ds = GraphManagementDataset::new();
820        let op = GraphManagementOp::Clear {
821            target: GraphManagementTarget::Named(g1()),
822            silent: true,
823        };
824        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_ok());
825    }
826
827    #[test]
828    fn test_clear_all_named_removes_all_named_graphs_content() {
829        let mut ds = GraphManagementDataset::new();
830        ds.add_triple(Some(&g1()), t1());
831        ds.add_triple(Some(&g2()), t2());
832        ds.add_triple(None, t3()); // default should remain untouched
833        let op = GraphManagementOp::Clear {
834            target: GraphManagementTarget::AllNamed,
835            silent: false,
836        };
837        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
838        assert_eq!(result.triples_affected, 2);
839        assert_eq!(ds.triple_count(Some(&g1())), 0);
840        assert_eq!(ds.triple_count(Some(&g2())), 0);
841        assert_eq!(ds.triple_count(None), 1); // default untouched
842    }
843
844    #[test]
845    fn test_clear_all_removes_everything() {
846        let mut ds = GraphManagementDataset::new();
847        ds.add_triple(None, t1());
848        ds.add_triple(Some(&g1()), t2());
849        ds.add_triple(Some(&g2()), t3());
850        let op = GraphManagementOp::Clear {
851            target: GraphManagementTarget::All,
852            silent: false,
853        };
854        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
855        assert_eq!(result.triples_affected, 3);
856        assert_eq!(ds.triple_count(None), 0);
857        assert_eq!(ds.triple_count(Some(&g1())), 0);
858        assert_eq!(ds.triple_count(Some(&g2())), 0);
859    }
860
861    #[test]
862    fn test_clear_all_on_empty_dataset_is_ok() {
863        let mut ds = GraphManagementDataset::new();
864        let op = GraphManagementOp::Clear {
865            target: GraphManagementTarget::All,
866            silent: false,
867        };
868        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
869        assert_eq!(result.triples_affected, 0);
870    }
871
872    #[test]
873    fn test_clear_returns_graphs_affected_list() {
874        let mut ds = GraphManagementDataset::new();
875        ds.add_triple(Some(&g1()), t1());
876        let op = GraphManagementOp::Clear {
877            target: GraphManagementTarget::Named(g1()),
878            silent: false,
879        };
880        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
881        assert!(result.graphs_affected.contains(&g1()));
882    }
883
884    // -----------------------------------------------------------------------
885    // DROP tests
886    // -----------------------------------------------------------------------
887
888    #[test]
889    fn test_drop_named_graph_removes_it() {
890        let mut ds = dataset_with_named_triples(&g1(), &[t1(), t2()]);
891        let op = GraphManagementOp::Drop {
892            target: GraphManagementTarget::Named(g1()),
893            silent: false,
894        };
895        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
896        assert!(!ds.named_graph_exists(&g1()));
897    }
898
899    #[test]
900    fn test_drop_named_graph_reports_triples_affected() {
901        let mut ds = dataset_with_named_triples(&g1(), &[t1(), t2(), t3()]);
902        let op = GraphManagementOp::Drop {
903            target: GraphManagementTarget::Named(g1()),
904            silent: false,
905        };
906        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
907        assert_eq!(result.triples_affected, 3);
908    }
909
910    #[test]
911    fn test_drop_silent_on_nonexistent_graph_succeeds() {
912        let mut ds = GraphManagementDataset::new();
913        let op = GraphManagementOp::Drop {
914            target: GraphManagementTarget::Named(g1()),
915            silent: true,
916        };
917        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_ok());
918    }
919
920    #[test]
921    fn test_drop_nonsilent_on_nonexistent_graph_errors() {
922        let mut ds = GraphManagementDataset::new();
923        let op = GraphManagementOp::Drop {
924            target: GraphManagementTarget::Named(g1()),
925            silent: false,
926        };
927        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_err());
928    }
929
930    #[test]
931    fn test_drop_default_clears_default_graph() {
932        let mut ds = dataset_with_default_triples(&[t1(), t2()]);
933        let op = GraphManagementOp::Drop {
934            target: GraphManagementTarget::Default,
935            silent: false,
936        };
937        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
938        assert_eq!(ds.triple_count(None), 0);
939    }
940
941    #[test]
942    fn test_drop_all_named_removes_all_named_graphs() {
943        let mut ds = GraphManagementDataset::new();
944        ds.add_triple(Some(&g1()), t1());
945        ds.add_triple(Some(&g2()), t2());
946        ds.add_triple(None, t3());
947        let op = GraphManagementOp::Drop {
948            target: GraphManagementTarget::AllNamed,
949            silent: false,
950        };
951        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
952        assert!(ds.graph_names().is_empty());
953        assert_eq!(ds.triple_count(None), 1); // default untouched
954    }
955
956    #[test]
957    fn test_drop_all_removes_default_and_named() {
958        let mut ds = GraphManagementDataset::new();
959        ds.add_triple(None, t1());
960        ds.add_triple(Some(&g1()), t2());
961        let op = GraphManagementOp::Drop {
962            target: GraphManagementTarget::All,
963            silent: false,
964        };
965        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
966        assert_eq!(result.triples_affected, 2);
967        assert_eq!(ds.triple_count(None), 0);
968        assert!(ds.graph_names().is_empty());
969    }
970
971    #[test]
972    fn test_drop_named_does_not_affect_other_named_graphs() {
973        let mut ds = GraphManagementDataset::new();
974        ds.add_triple(Some(&g1()), t1());
975        ds.add_triple(Some(&g2()), t2());
976        let op = GraphManagementOp::Drop {
977            target: GraphManagementTarget::Named(g1()),
978            silent: false,
979        };
980        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
981        assert!(!ds.named_graph_exists(&g1()));
982        assert!(ds.named_graph_exists(&g2()));
983    }
984
985    // -----------------------------------------------------------------------
986    // CREATE tests
987    // -----------------------------------------------------------------------
988
989    #[test]
990    fn test_create_creates_empty_named_graph() {
991        let mut ds = GraphManagementDataset::new();
992        let op = GraphManagementOp::Create {
993            graph: g1(),
994            silent: false,
995        };
996        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
997        assert!(ds.named_graph_exists(&g1()));
998        assert_eq!(ds.triple_count(Some(&g1())), 0);
999    }
1000
1001    #[test]
1002    fn test_create_reports_affected_graph() {
1003        let mut ds = GraphManagementDataset::new();
1004        let op = GraphManagementOp::Create {
1005            graph: g1(),
1006            silent: false,
1007        };
1008        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1009        assert!(result.graphs_affected.contains(&g1()));
1010    }
1011
1012    #[test]
1013    fn test_create_existing_graph_without_silent_errors() {
1014        let mut ds = GraphManagementDataset::new();
1015        ds.named_graphs.insert(g1(), vec![]);
1016        let op = GraphManagementOp::Create {
1017            graph: g1(),
1018            silent: false,
1019        };
1020        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_err());
1021    }
1022
1023    #[test]
1024    fn test_create_silent_on_existing_graph_succeeds() {
1025        let mut ds = GraphManagementDataset::new();
1026        ds.named_graphs.insert(g1(), vec![t1()]);
1027        let op = GraphManagementOp::Create {
1028            graph: g1(),
1029            silent: true,
1030        };
1031        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1032        // Content must be preserved
1033        assert_eq!(ds.triple_count(Some(&g1())), 1);
1034        assert_eq!(result.triples_affected, 0);
1035    }
1036
1037    #[test]
1038    fn test_create_multiple_graphs() {
1039        let mut ds = GraphManagementDataset::new();
1040        for g in [&g1(), &g2(), &g3()] {
1041            GraphManagementExecutor::execute(
1042                &GraphManagementOp::Create {
1043                    graph: g.clone(),
1044                    silent: false,
1045                },
1046                &mut ds,
1047            )
1048            .unwrap();
1049        }
1050        assert_eq!(ds.graph_names().len(), 3);
1051    }
1052
1053    // -----------------------------------------------------------------------
1054    // COPY tests
1055    // -----------------------------------------------------------------------
1056
1057    #[test]
1058    fn test_copy_named_to_named_copies_triples() {
1059        let mut ds = GraphManagementDataset::new();
1060        ds.add_triple(Some(&g1()), t1());
1061        ds.add_triple(Some(&g1()), t2());
1062        let op = GraphManagementOp::Copy {
1063            source: GraphManagementTarget::Named(g1()),
1064            destination: GraphManagementTarget::Named(g2()),
1065            silent: false,
1066        };
1067        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1068        assert_eq!(ds.triple_count(Some(&g2())), 2);
1069    }
1070
1071    #[test]
1072    fn test_copy_clears_destination_first() {
1073        let mut ds = GraphManagementDataset::new();
1074        ds.add_triple(Some(&g1()), t1());
1075        ds.add_triple(Some(&g2()), t3()); // pre-existing in dest
1076        let op = GraphManagementOp::Copy {
1077            source: GraphManagementTarget::Named(g1()),
1078            destination: GraphManagementTarget::Named(g2()),
1079            silent: false,
1080        };
1081        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1082        // Destination should contain only what was in source
1083        assert_eq!(ds.triple_count(Some(&g2())), 1);
1084        assert_eq!(ds.get_graph(Some(&g2()))[0], t1());
1085    }
1086
1087    #[test]
1088    fn test_copy_leaves_source_unchanged() {
1089        let mut ds = GraphManagementDataset::new();
1090        ds.add_triple(Some(&g1()), t1());
1091        ds.add_triple(Some(&g1()), t2());
1092        let op = GraphManagementOp::Copy {
1093            source: GraphManagementTarget::Named(g1()),
1094            destination: GraphManagementTarget::Named(g2()),
1095            silent: false,
1096        };
1097        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1098        assert_eq!(ds.triple_count(Some(&g1())), 2);
1099    }
1100
1101    #[test]
1102    fn test_copy_from_default_to_named() {
1103        let mut ds = dataset_with_default_triples(&[t1(), t2()]);
1104        let op = GraphManagementOp::Copy {
1105            source: GraphManagementTarget::Default,
1106            destination: GraphManagementTarget::Named(g1()),
1107            silent: false,
1108        };
1109        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1110        assert_eq!(ds.triple_count(Some(&g1())), 2);
1111        assert_eq!(ds.triple_count(None), 2); // source unchanged
1112    }
1113
1114    #[test]
1115    fn test_copy_from_named_to_default() {
1116        let mut ds = GraphManagementDataset::new();
1117        ds.add_triple(Some(&g1()), t1());
1118        ds.add_triple(None, t3()); // pre-existing default should be cleared
1119        let op = GraphManagementOp::Copy {
1120            source: GraphManagementTarget::Named(g1()),
1121            destination: GraphManagementTarget::Default,
1122            silent: false,
1123        };
1124        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1125        assert_eq!(ds.triple_count(None), 1);
1126        assert_eq!(ds.get_graph(None)[0], t1());
1127    }
1128
1129    #[test]
1130    fn test_copy_self_to_self_is_noop() {
1131        let mut ds = GraphManagementDataset::new();
1132        ds.add_triple(Some(&g1()), t1());
1133        let op = GraphManagementOp::Copy {
1134            source: GraphManagementTarget::Named(g1()),
1135            destination: GraphManagementTarget::Named(g1()),
1136            silent: false,
1137        };
1138        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1139        assert_eq!(result.triples_affected, 0);
1140        assert_eq!(ds.triple_count(Some(&g1())), 1); // content preserved
1141    }
1142
1143    #[test]
1144    fn test_copy_nonexistent_source_without_silent_errors() {
1145        let mut ds = GraphManagementDataset::new();
1146        let op = GraphManagementOp::Copy {
1147            source: GraphManagementTarget::Named(g1()),
1148            destination: GraphManagementTarget::Named(g2()),
1149            silent: false,
1150        };
1151        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_err());
1152    }
1153
1154    #[test]
1155    fn test_copy_nonexistent_source_with_silent_succeeds() {
1156        let mut ds = GraphManagementDataset::new();
1157        let op = GraphManagementOp::Copy {
1158            source: GraphManagementTarget::Named(g1()),
1159            destination: GraphManagementTarget::Named(g2()),
1160            silent: true,
1161        };
1162        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_ok());
1163    }
1164
1165    #[test]
1166    fn test_copy_reports_triples_affected() {
1167        let mut ds = GraphManagementDataset::new();
1168        ds.add_triple(Some(&g1()), t1());
1169        ds.add_triple(Some(&g1()), t2());
1170        let op = GraphManagementOp::Copy {
1171            source: GraphManagementTarget::Named(g1()),
1172            destination: GraphManagementTarget::Named(g2()),
1173            silent: false,
1174        };
1175        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1176        assert_eq!(result.triples_affected, 2);
1177    }
1178
1179    // -----------------------------------------------------------------------
1180    // MOVE tests
1181    // -----------------------------------------------------------------------
1182
1183    #[test]
1184    fn test_move_named_to_named_copies_then_drops_source() {
1185        let mut ds = GraphManagementDataset::new();
1186        ds.add_triple(Some(&g1()), t1());
1187        ds.add_triple(Some(&g1()), t2());
1188        let op = GraphManagementOp::Move {
1189            source: GraphManagementTarget::Named(g1()),
1190            destination: GraphManagementTarget::Named(g2()),
1191            silent: false,
1192        };
1193        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1194        assert_eq!(ds.triple_count(Some(&g2())), 2);
1195        assert!(!ds.named_graph_exists(&g1())); // source dropped
1196    }
1197
1198    #[test]
1199    fn test_move_clears_destination_before_writing() {
1200        let mut ds = GraphManagementDataset::new();
1201        ds.add_triple(Some(&g1()), t1());
1202        ds.add_triple(Some(&g2()), t3()); // pre-existing
1203        let op = GraphManagementOp::Move {
1204            source: GraphManagementTarget::Named(g1()),
1205            destination: GraphManagementTarget::Named(g2()),
1206            silent: false,
1207        };
1208        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1209        assert_eq!(ds.triple_count(Some(&g2())), 1);
1210        assert_eq!(ds.get_graph(Some(&g2()))[0], t1());
1211    }
1212
1213    #[test]
1214    fn test_move_from_default_to_named() {
1215        let mut ds = dataset_with_default_triples(&[t1(), t2()]);
1216        let op = GraphManagementOp::Move {
1217            source: GraphManagementTarget::Default,
1218            destination: GraphManagementTarget::Named(g1()),
1219            silent: false,
1220        };
1221        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1222        assert_eq!(ds.triple_count(Some(&g1())), 2);
1223        assert_eq!(ds.triple_count(None), 0); // default cleared
1224    }
1225
1226    #[test]
1227    fn test_move_from_named_to_default() {
1228        let mut ds = GraphManagementDataset::new();
1229        ds.add_triple(Some(&g1()), t1());
1230        let op = GraphManagementOp::Move {
1231            source: GraphManagementTarget::Named(g1()),
1232            destination: GraphManagementTarget::Default,
1233            silent: false,
1234        };
1235        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1236        assert_eq!(ds.triple_count(None), 1);
1237        assert!(!ds.named_graph_exists(&g1()));
1238    }
1239
1240    #[test]
1241    fn test_move_self_to_self_is_noop() {
1242        let mut ds = GraphManagementDataset::new();
1243        ds.add_triple(Some(&g1()), t1());
1244        let op = GraphManagementOp::Move {
1245            source: GraphManagementTarget::Named(g1()),
1246            destination: GraphManagementTarget::Named(g1()),
1247            silent: false,
1248        };
1249        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1250        assert_eq!(result.triples_affected, 0);
1251        // Content preserved
1252        assert_eq!(ds.triple_count(Some(&g1())), 1);
1253    }
1254
1255    #[test]
1256    fn test_move_nonexistent_source_without_silent_errors() {
1257        let mut ds = GraphManagementDataset::new();
1258        let op = GraphManagementOp::Move {
1259            source: GraphManagementTarget::Named(g1()),
1260            destination: GraphManagementTarget::Named(g2()),
1261            silent: false,
1262        };
1263        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_err());
1264    }
1265
1266    #[test]
1267    fn test_move_nonexistent_source_with_silent_succeeds() {
1268        let mut ds = GraphManagementDataset::new();
1269        let op = GraphManagementOp::Move {
1270            source: GraphManagementTarget::Named(g1()),
1271            destination: GraphManagementTarget::Named(g2()),
1272            silent: true,
1273        };
1274        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_ok());
1275    }
1276
1277    #[test]
1278    fn test_move_reports_triples_affected() {
1279        let mut ds = GraphManagementDataset::new();
1280        ds.add_triple(Some(&g1()), t1());
1281        ds.add_triple(Some(&g1()), t2());
1282        ds.add_triple(Some(&g1()), t3());
1283        let op = GraphManagementOp::Move {
1284            source: GraphManagementTarget::Named(g1()),
1285            destination: GraphManagementTarget::Named(g2()),
1286            silent: false,
1287        };
1288        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1289        assert_eq!(result.triples_affected, 3);
1290    }
1291
1292    // -----------------------------------------------------------------------
1293    // ADD tests
1294    // -----------------------------------------------------------------------
1295
1296    #[test]
1297    fn test_add_merges_triples_into_destination() {
1298        let mut ds = GraphManagementDataset::new();
1299        ds.add_triple(Some(&g1()), t1());
1300        ds.add_triple(Some(&g2()), t2());
1301        let op = GraphManagementOp::Add {
1302            source: GraphManagementTarget::Named(g1()),
1303            destination: GraphManagementTarget::Named(g2()),
1304            silent: false,
1305        };
1306        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1307        assert_eq!(ds.triple_count(Some(&g2())), 2); // t2 + t1
1308    }
1309
1310    #[test]
1311    fn test_add_does_not_clear_destination() {
1312        let mut ds = GraphManagementDataset::new();
1313        ds.add_triple(Some(&g1()), t1());
1314        ds.add_triple(Some(&g2()), t2()); // pre-existing
1315        let op = GraphManagementOp::Add {
1316            source: GraphManagementTarget::Named(g1()),
1317            destination: GraphManagementTarget::Named(g2()),
1318            silent: false,
1319        };
1320        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1321        // Both original and new triples must be present
1322        let g2_triples = ds.get_graph(Some(&g2()));
1323        assert!(g2_triples.contains(&t1()));
1324        assert!(g2_triples.contains(&t2()));
1325    }
1326
1327    #[test]
1328    fn test_add_leaves_source_unchanged() {
1329        let mut ds = GraphManagementDataset::new();
1330        ds.add_triple(Some(&g1()), t1());
1331        let op = GraphManagementOp::Add {
1332            source: GraphManagementTarget::Named(g1()),
1333            destination: GraphManagementTarget::Named(g2()),
1334            silent: false,
1335        };
1336        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1337        assert_eq!(ds.triple_count(Some(&g1())), 1);
1338    }
1339
1340    #[test]
1341    fn test_add_from_default_to_named() {
1342        let mut ds = dataset_with_default_triples(&[t1(), t2()]);
1343        ds.add_triple(Some(&g1()), t3());
1344        let op = GraphManagementOp::Add {
1345            source: GraphManagementTarget::Default,
1346            destination: GraphManagementTarget::Named(g1()),
1347            silent: false,
1348        };
1349        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1350        assert_eq!(ds.triple_count(Some(&g1())), 3);
1351        assert_eq!(ds.triple_count(None), 2); // default unchanged
1352    }
1353
1354    #[test]
1355    fn test_add_from_named_to_default() {
1356        let mut ds = GraphManagementDataset::new();
1357        ds.add_triple(Some(&g1()), t1());
1358        ds.add_triple(None, t2());
1359        let op = GraphManagementOp::Add {
1360            source: GraphManagementTarget::Named(g1()),
1361            destination: GraphManagementTarget::Default,
1362            silent: false,
1363        };
1364        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1365        assert_eq!(ds.triple_count(None), 2);
1366        assert_eq!(ds.triple_count(Some(&g1())), 1); // unchanged
1367    }
1368
1369    #[test]
1370    fn test_add_self_to_self_is_noop() {
1371        let mut ds = GraphManagementDataset::new();
1372        ds.add_triple(Some(&g1()), t1());
1373        let op = GraphManagementOp::Add {
1374            source: GraphManagementTarget::Named(g1()),
1375            destination: GraphManagementTarget::Named(g1()),
1376            silent: false,
1377        };
1378        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1379        assert_eq!(result.triples_affected, 0);
1380        assert_eq!(ds.triple_count(Some(&g1())), 1);
1381    }
1382
1383    #[test]
1384    fn test_add_nonexistent_source_without_silent_errors() {
1385        let mut ds = GraphManagementDataset::new();
1386        let op = GraphManagementOp::Add {
1387            source: GraphManagementTarget::Named(g1()),
1388            destination: GraphManagementTarget::Named(g2()),
1389            silent: false,
1390        };
1391        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_err());
1392    }
1393
1394    #[test]
1395    fn test_add_nonexistent_source_with_silent_succeeds() {
1396        let mut ds = GraphManagementDataset::new();
1397        let op = GraphManagementOp::Add {
1398            source: GraphManagementTarget::Named(g1()),
1399            destination: GraphManagementTarget::Named(g2()),
1400            silent: true,
1401        };
1402        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_ok());
1403    }
1404
1405    #[test]
1406    fn test_add_reports_triples_affected() {
1407        let mut ds = GraphManagementDataset::new();
1408        ds.add_triple(Some(&g1()), t1());
1409        ds.add_triple(Some(&g1()), t2());
1410        let op = GraphManagementOp::Add {
1411            source: GraphManagementTarget::Named(g1()),
1412            destination: GraphManagementTarget::Named(g2()),
1413            silent: false,
1414        };
1415        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1416        assert_eq!(result.triples_affected, 2);
1417    }
1418
1419    // -----------------------------------------------------------------------
1420    // LOAD tests
1421    // -----------------------------------------------------------------------
1422
1423    #[test]
1424    fn test_load_without_silent_returns_error() {
1425        let mut ds = GraphManagementDataset::new();
1426        let op = GraphManagementOp::Load {
1427            iri: "http://example.org/data.ttl".to_owned(),
1428            into_graph: None,
1429            silent: false,
1430        };
1431        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_err());
1432    }
1433
1434    #[test]
1435    fn test_load_with_silent_returns_ok() {
1436        let mut ds = GraphManagementDataset::new();
1437        let op = GraphManagementOp::Load {
1438            iri: "http://example.org/data.ttl".to_owned(),
1439            into_graph: None,
1440            silent: true,
1441        };
1442        assert!(GraphManagementExecutor::execute(&op, &mut ds).is_ok());
1443    }
1444
1445    #[test]
1446    fn test_load_silent_into_named_graph_returns_ok() {
1447        let mut ds = GraphManagementDataset::new();
1448        let op = GraphManagementOp::Load {
1449            iri: "http://example.org/data.ttl".to_owned(),
1450            into_graph: Some(g1()),
1451            silent: true,
1452        };
1453        let result = GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1454        // Silent load is a no-op; dataset unchanged
1455        assert_eq!(result.triples_affected, 0);
1456    }
1457
1458    // -----------------------------------------------------------------------
1459    // Composed / interaction tests
1460    // -----------------------------------------------------------------------
1461
1462    #[test]
1463    fn test_create_then_add_then_clear_lifecycle() {
1464        let mut ds = GraphManagementDataset::new();
1465
1466        // 1. CREATE
1467        GraphManagementExecutor::execute(
1468            &GraphManagementOp::Create {
1469                graph: g1(),
1470                silent: false,
1471            },
1472            &mut ds,
1473        )
1474        .unwrap();
1475        assert!(ds.named_graph_exists(&g1()));
1476
1477        // 2. ADD from default (empty) to g1 — no-op effectively
1478        ds.add_triple(None, t1());
1479        GraphManagementExecutor::execute(
1480            &GraphManagementOp::Add {
1481                source: GraphManagementTarget::Default,
1482                destination: GraphManagementTarget::Named(g1()),
1483                silent: false,
1484            },
1485            &mut ds,
1486        )
1487        .unwrap();
1488        assert_eq!(ds.triple_count(Some(&g1())), 1);
1489
1490        // 3. CLEAR g1
1491        GraphManagementExecutor::execute(
1492            &GraphManagementOp::Clear {
1493                target: GraphManagementTarget::Named(g1()),
1494                silent: false,
1495            },
1496            &mut ds,
1497        )
1498        .unwrap();
1499        assert_eq!(ds.triple_count(Some(&g1())), 0);
1500        assert!(ds.named_graph_exists(&g1())); // graph still exists after CLEAR
1501    }
1502
1503    #[test]
1504    fn test_move_then_drop_leaves_clean_dataset() {
1505        let mut ds = GraphManagementDataset::new();
1506        ds.add_triple(Some(&g1()), t1());
1507        ds.add_triple(Some(&g1()), t2());
1508
1509        // MOVE g1 -> g2
1510        GraphManagementExecutor::execute(
1511            &GraphManagementOp::Move {
1512                source: GraphManagementTarget::Named(g1()),
1513                destination: GraphManagementTarget::Named(g2()),
1514                silent: false,
1515            },
1516            &mut ds,
1517        )
1518        .unwrap();
1519
1520        // DROP g2
1521        GraphManagementExecutor::execute(
1522            &GraphManagementOp::Drop {
1523                target: GraphManagementTarget::Named(g2()),
1524                silent: false,
1525            },
1526            &mut ds,
1527        )
1528        .unwrap();
1529
1530        assert!(ds.graph_names().is_empty());
1531    }
1532
1533    #[test]
1534    fn test_copy_then_add_accumulates_duplicates() {
1535        let mut ds = GraphManagementDataset::new();
1536        ds.add_triple(Some(&g1()), t1());
1537
1538        // COPY g1 -> g2
1539        GraphManagementExecutor::execute(
1540            &GraphManagementOp::Copy {
1541                source: GraphManagementTarget::Named(g1()),
1542                destination: GraphManagementTarget::Named(g2()),
1543                silent: false,
1544            },
1545            &mut ds,
1546        )
1547        .unwrap();
1548
1549        // ADD g1 -> g2 (g2 already has the triple; duplicates are allowed in a bag model)
1550        GraphManagementExecutor::execute(
1551            &GraphManagementOp::Add {
1552                source: GraphManagementTarget::Named(g1()),
1553                destination: GraphManagementTarget::Named(g2()),
1554                silent: false,
1555            },
1556            &mut ds,
1557        )
1558        .unwrap();
1559
1560        assert_eq!(ds.triple_count(Some(&g2())), 2);
1561    }
1562
1563    #[test]
1564    fn test_graph_management_result_default() {
1565        let r = GraphManagementResult::default();
1566        assert_eq!(r.triples_affected, 0);
1567        assert!(r.graphs_affected.is_empty());
1568    }
1569
1570    #[test]
1571    fn test_target_label_default() {
1572        let label = GraphManagementExecutor::target_label(&GraphManagementTarget::Default);
1573        assert_eq!(label, "DEFAULT");
1574    }
1575
1576    #[test]
1577    fn test_target_label_named() {
1578        let label = GraphManagementExecutor::target_label(&GraphManagementTarget::Named(g1()));
1579        assert_eq!(label, g1());
1580    }
1581
1582    #[test]
1583    fn test_target_label_all() {
1584        let label = GraphManagementExecutor::target_label(&GraphManagementTarget::All);
1585        assert_eq!(label, "ALL");
1586    }
1587
1588    #[test]
1589    fn test_target_label_all_named() {
1590        let label = GraphManagementExecutor::target_label(&GraphManagementTarget::AllNamed);
1591        assert_eq!(label, "NAMED");
1592    }
1593
1594    #[test]
1595    fn test_graph_management_target_equality() {
1596        assert_eq!(
1597            GraphManagementTarget::Default,
1598            GraphManagementTarget::Default
1599        );
1600        assert_eq!(
1601            GraphManagementTarget::Named(g1()),
1602            GraphManagementTarget::Named(g1())
1603        );
1604        assert_ne!(
1605            GraphManagementTarget::Named(g1()),
1606            GraphManagementTarget::Named(g2())
1607        );
1608        assert_ne!(GraphManagementTarget::All, GraphManagementTarget::AllNamed);
1609    }
1610
1611    #[test]
1612    fn test_add_to_nonexistent_destination_creates_it() {
1613        let mut ds = GraphManagementDataset::new();
1614        ds.add_triple(Some(&g1()), t1());
1615        // g2 does not exist yet
1616        let op = GraphManagementOp::Add {
1617            source: GraphManagementTarget::Named(g1()),
1618            destination: GraphManagementTarget::Named(g2()),
1619            silent: false,
1620        };
1621        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1622        assert_eq!(ds.triple_count(Some(&g2())), 1);
1623    }
1624
1625    #[test]
1626    fn test_copy_to_nonexistent_destination_creates_it() {
1627        let mut ds = GraphManagementDataset::new();
1628        ds.add_triple(Some(&g1()), t1());
1629        let op = GraphManagementOp::Copy {
1630            source: GraphManagementTarget::Named(g1()),
1631            destination: GraphManagementTarget::Named(g2()),
1632            silent: false,
1633        };
1634        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1635        assert!(ds.named_graph_exists(&g2()));
1636        assert_eq!(ds.triple_count(Some(&g2())), 1);
1637    }
1638
1639    #[test]
1640    fn test_move_to_nonexistent_destination_creates_it() {
1641        let mut ds = GraphManagementDataset::new();
1642        ds.add_triple(Some(&g1()), t1());
1643        let op = GraphManagementOp::Move {
1644            source: GraphManagementTarget::Named(g1()),
1645            destination: GraphManagementTarget::Named(g2()),
1646            silent: false,
1647        };
1648        GraphManagementExecutor::execute(&op, &mut ds).unwrap();
1649        assert!(ds.named_graph_exists(&g2()));
1650        assert!(!ds.named_graph_exists(&g1()));
1651    }
1652
1653    #[test]
1654    fn test_clear_named_graph_after_multiple_adds() {
1655        let mut ds = GraphManagementDataset::new();
1656        for t in [t1(), t2(), t3()] {
1657            ds.add_triple(Some(&g1()), t);
1658        }
1659        GraphManagementExecutor::execute(
1660            &GraphManagementOp::Clear {
1661                target: GraphManagementTarget::Named(g1()),
1662                silent: false,
1663            },
1664            &mut ds,
1665        )
1666        .unwrap();
1667        assert_eq!(ds.triple_count(Some(&g1())), 0);
1668    }
1669
1670    #[test]
1671    fn test_drop_all_named_on_empty_dataset_ok() {
1672        let mut ds = GraphManagementDataset::new();
1673        let result = GraphManagementExecutor::execute(
1674            &GraphManagementOp::Drop {
1675                target: GraphManagementTarget::AllNamed,
1676                silent: false,
1677            },
1678            &mut ds,
1679        )
1680        .unwrap();
1681        assert_eq!(result.triples_affected, 0);
1682    }
1683}