Skip to main content

oxirs_core/
oxigraph_compat.rs

1//! Oxigraph compatibility layer
2//!
3//! This module provides a compatibility layer that matches Oxigraph's API,
4//! allowing OxiRS to be used as a drop-in replacement for Oxigraph.
5
6use crate::{
7    model::*,
8    parser::RdfFormat,
9    rdf_store::{OxirsQueryResults, RdfStore},
10    transaction::{IsolationLevel, TransactionManager},
11    OxirsError, Result, Store as OxirsStoreTrait,
12};
13use std::io::{BufRead, Write};
14use std::path::{Path, PathBuf};
15use std::sync::{Arc, RwLock};
16
17/// Oxigraph-compatible store implementation
18///
19/// This provides the same API as oxigraph::Store for compatibility
20///
21/// Uses interior mutability to match Oxigraph's API where mutations take &self
22pub struct Store {
23    inner: Arc<RwLock<RdfStore>>,
24    tx_manager: Arc<RwLock<Option<TransactionManager>>>,
25    wal_dir: Option<PathBuf>,
26}
27
28impl Store {
29    /// Creates a new in-memory store
30    ///
31    /// This matches oxigraph::Store::new()
32    pub fn new() -> Result<Self> {
33        Ok(Store {
34            inner: Arc::new(RwLock::new(RdfStore::new()?)),
35            tx_manager: Arc::new(RwLock::new(None)),
36            wal_dir: None,
37        })
38    }
39
40    /// Opens a persistent store at the given path
41    ///
42    /// This matches oxigraph::Store::open()
43    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
44        let path_buf = path.as_ref().to_path_buf();
45        let wal_dir = path_buf.join("wal");
46
47        Ok(Store {
48            inner: Arc::new(RwLock::new(RdfStore::open(&path_buf)?)),
49            tx_manager: Arc::new(RwLock::new(None)),
50            wal_dir: Some(wal_dir),
51        })
52    }
53
54    /// Inserts a quad into the store
55    ///
56    /// Returns true if the quad was not already present
57    pub fn insert<'a>(&self, quad: impl Into<QuadRef<'a>>) -> Result<bool> {
58        let quad_ref = quad.into();
59        let quad = Quad::new(
60            quad_ref.subject().to_owned(),
61            quad_ref.predicate().to_owned(),
62            quad_ref.object().to_owned(),
63            quad_ref.graph_name().to_owned(),
64        );
65
66        let store = self
67            .inner
68            .write()
69            .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
70        store.insert_quad(quad)
71    }
72
73    /// Extends the store with an iterator of quads
74    pub fn extend<'a>(
75        &self,
76        quads: impl IntoIterator<Item = impl Into<QuadRef<'a>>>,
77    ) -> Result<()> {
78        let store = self
79            .inner
80            .write()
81            .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
82
83        for quad in quads {
84            let quad_ref = quad.into();
85            let quad = Quad::new(
86                quad_ref.subject().to_owned(),
87                quad_ref.predicate().to_owned(),
88                quad_ref.object().to_owned(),
89                quad_ref.graph_name().to_owned(),
90            );
91            store.insert_quad(quad)?;
92        }
93
94        Ok(())
95    }
96
97    /// Removes a quad from the store
98    ///
99    /// Returns true if the quad was present
100    pub fn remove<'a>(&self, quad: impl Into<QuadRef<'a>>) -> Result<bool> {
101        let quad_ref = quad.into();
102        let quad = Quad::new(
103            quad_ref.subject().to_owned(),
104            quad_ref.predicate().to_owned(),
105            quad_ref.object().to_owned(),
106            quad_ref.graph_name().to_owned(),
107        );
108
109        let store = self
110            .inner
111            .write()
112            .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
113        store.remove_quad(&quad)
114    }
115
116    /// Loads a file into the store
117    pub fn load_from_reader<R: BufRead>(
118        &self,
119        reader: R,
120        format: RdfFormat,
121        base_iri: Option<&str>,
122        graph: Option<impl Into<GraphName>>,
123    ) -> Result<()> {
124        use crate::parser::Parser;
125
126        // Read all data into a string
127        let mut data = String::new();
128        let mut reader = reader;
129        // BufRead already extends Read, so this import is not needed
130        reader
131            .read_to_string(&mut data)
132            .map_err(|e| OxirsError::Parse(format!("Failed to read input: {e}")))?;
133
134        // Create parser with base IRI if provided
135        let mut parser = Parser::new(format);
136        if let Some(base) = base_iri {
137            parser = parser.with_base_iri(base);
138        }
139
140        // Parse to quads
141        let quads = parser.parse_str_to_quads(&data)?;
142
143        // Get write lock on store
144        let store = self
145            .inner
146            .write()
147            .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
148
149        // Insert quads, potentially modifying graph name
150        let target_graph = graph.map(|g| g.into());
151        for quad in quads {
152            let final_quad = if let Some(ref g) = target_graph {
153                // Override the quad's graph with the specified one
154                Quad::new(
155                    quad.subject().clone(),
156                    quad.predicate().clone(),
157                    quad.object().clone(),
158                    g.clone(),
159                )
160            } else {
161                quad
162            };
163            store.insert_quad(final_quad)?;
164        }
165
166        Ok(())
167    }
168
169    /// Dumps the store content to a writer
170    pub fn dump_to_writer<'a, W: Write>(
171        &self,
172        mut writer: W,
173        format: RdfFormat,
174        graph: Option<impl Into<GraphNameRef<'a>>>,
175    ) -> Result<()> {
176        use crate::model::{dataset::Dataset, graph::Graph};
177        use crate::serializer::Serializer;
178
179        let store = self
180            .inner
181            .read()
182            .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
183
184        let serializer = Serializer::new(format);
185
186        // Get quads to serialize
187        let quads = if let Some(g) = graph {
188            let graph_ref = g.into();
189            let graph_name = graph_ref.to_owned();
190            store.query_quads(None, None, None, Some(&graph_name))?
191        } else {
192            store.iter_quads()?
193        };
194
195        // Serialize based on format capabilities
196        let output = match format {
197            RdfFormat::Turtle | RdfFormat::NTriples | RdfFormat::RdfXml => {
198                // These formats only support triples, so filter to default graph
199                let triples: Vec<_> = quads
200                    .into_iter()
201                    .filter(|q| q.is_default_graph())
202                    .map(|q| q.to_triple())
203                    .collect();
204                let graph = Graph::from_iter(triples);
205                serializer.serialize_graph(&graph)?
206            }
207            RdfFormat::TriG | RdfFormat::NQuads | RdfFormat::JsonLd => {
208                // These formats support quads/datasets
209                let dataset = Dataset::from_iter(quads);
210                serializer.serialize_dataset(&dataset)?
211            }
212        };
213
214        writer
215            .write_all(output.as_bytes())
216            .map_err(|e| OxirsError::Serialize(format!("Failed to write output: {e}")))?;
217
218        Ok(())
219    }
220
221    /// Checks if the store contains a given quad
222    pub fn contains<'a>(&self, quad: impl Into<QuadRef<'a>>) -> Result<bool> {
223        let quad_ref = quad.into();
224        let quad = Quad::new(
225            quad_ref.subject().to_owned(),
226            quad_ref.predicate().to_owned(),
227            quad_ref.object().to_owned(),
228            quad_ref.graph_name().to_owned(),
229        );
230
231        let store = self
232            .inner
233            .read()
234            .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
235        store.contains_quad(&quad)
236    }
237
238    /// Returns the number of quads in the store
239    pub fn len(&self) -> Result<usize> {
240        let store = self
241            .inner
242            .read()
243            .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
244        store.len()
245    }
246
247    /// Checks if the store is empty
248    pub fn is_empty(&self) -> Result<bool> {
249        let store = self
250            .inner
251            .read()
252            .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
253        store.is_empty()
254    }
255
256    /// Returns an iterator over all quads matching a pattern
257    pub fn quads_for_pattern<'a>(
258        &self,
259        subject: Option<impl Into<SubjectRef<'a>>>,
260        predicate: Option<impl Into<PredicateRef<'a>>>,
261        object: Option<impl Into<ObjectRef<'a>>>,
262        graph_name: Option<impl Into<GraphNameRef<'a>>>,
263    ) -> QuadIter {
264        let subject = subject.map(|s| {
265            let s_ref = s.into();
266            match s_ref {
267                SubjectRef::NamedNode(n) => Subject::NamedNode(n.to_owned()),
268                SubjectRef::BlankNode(b) => Subject::BlankNode(b.to_owned()),
269                SubjectRef::Variable(v) => Subject::Variable(v.to_owned()),
270            }
271        });
272
273        let predicate = predicate.map(|p| {
274            let p_ref = p.into();
275            match p_ref {
276                PredicateRef::NamedNode(n) => Predicate::NamedNode(n.to_owned()),
277                PredicateRef::Variable(v) => Predicate::Variable(v.to_owned()),
278            }
279        });
280
281        let object = object.map(|o| {
282            let o_ref = o.into();
283            match o_ref {
284                ObjectRef::NamedNode(n) => Object::NamedNode(n.to_owned()),
285                ObjectRef::BlankNode(b) => Object::BlankNode(b.to_owned()),
286                ObjectRef::Literal(l) => Object::Literal(l.to_owned()),
287                ObjectRef::Variable(v) => Object::Variable(v.to_owned()),
288            }
289        });
290
291        let graph_name = graph_name.map(|g| {
292            let g_ref = g.into();
293            g_ref.to_owned()
294        });
295
296        // Query the inner store
297        let quads = match self.inner.read() {
298            Ok(store) => store
299                .query_quads(
300                    subject.as_ref(),
301                    predicate.as_ref(),
302                    object.as_ref(),
303                    graph_name.as_ref(),
304                )
305                .unwrap_or_default(),
306            _ => Vec::new(),
307        };
308
309        QuadIter { quads, index: 0 }
310    }
311
312    /// Returns an iterator over all quads in the store
313    pub fn iter(&self) -> QuadIter {
314        self.quads_for_pattern(
315            None::<SubjectRef>,
316            None::<PredicateRef>,
317            None::<ObjectRef>,
318            None::<GraphNameRef>,
319        )
320    }
321
322    /// Returns all named graphs in the store
323    pub fn named_graphs(&self) -> GraphNameIter {
324        // Collect unique graph names from all quads
325        let mut graph_names = std::collections::HashSet::new();
326        if let Ok(store) = self.inner.read() {
327            if let Ok(quads) = store.iter_quads() {
328                for quad in quads {
329                    if let GraphName::NamedNode(n) = quad.graph_name() {
330                        graph_names.insert(n.clone());
331                    }
332                }
333            }
334        }
335
336        GraphNameIter {
337            graphs: graph_names.into_iter().collect(),
338            index: 0,
339        }
340    }
341
342    /// Checks if the store contains a given named graph
343    pub fn contains_named_graph<'a>(
344        &self,
345        graph_name: impl Into<NamedOrBlankNodeRef<'a>>,
346    ) -> Result<bool> {
347        let graph_ref = graph_name.into();
348        let graph = match graph_ref {
349            NamedOrBlankNodeRef::NamedNode(n) => GraphName::NamedNode(n.to_owned()),
350            NamedOrBlankNodeRef::BlankNode(b) => GraphName::BlankNode(b.to_owned()),
351        };
352
353        // Check if any quads exist in this graph
354        let store = self
355            .inner
356            .read()
357            .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
358        let quads = store.query_quads(None, None, None, Some(&graph))?;
359        Ok(!quads.is_empty())
360    }
361
362    /// Clears the store
363    pub fn clear(&self) -> Result<()> {
364        let mut store = self
365            .inner
366            .write()
367            .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
368        store.clear()
369    }
370
371    /// Clears a specific graph
372    pub fn clear_graph<'a>(&self, graph_name: impl Into<GraphNameRef<'a>>) -> Result<()> {
373        let graph_ref = graph_name.into();
374        let graph = graph_ref.to_owned();
375
376        let store = self
377            .inner
378            .write()
379            .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
380
381        // Get all quads in the specified graph
382        let quads_to_remove = store.query_quads(None, None, None, Some(&graph))?;
383
384        // Remove each quad
385        for quad in quads_to_remove {
386            store.remove_quad(&quad)?;
387        }
388
389        Ok(())
390    }
391
392    /// Executes a SPARQL query
393    pub fn query(&self, query: &str) -> Result<QueryResults> {
394        let store = self
395            .inner
396            .read()
397            .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
398        let results = store.query(query)?;
399        Ok(QueryResults { inner: results })
400    }
401
402    /// Executes a SPARQL update
403    pub fn update(&self, update_str: &str) -> Result<()> {
404        use crate::query::{UpdateExecutor, UpdateParser};
405
406        // Parse the UPDATE string
407        let parser = UpdateParser::new();
408        let update = parser.parse(update_str)?;
409
410        // Get write access to the store
411        let store = self
412            .inner
413            .write()
414            .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
415
416        // Execute the update
417        let executor = UpdateExecutor::new(&*store);
418        executor.execute(&update)?;
419
420        Ok(())
421    }
422
423    /// Creates a transaction for the store
424    ///
425    /// This method provides ACID transaction support with automatic commit/abort handling.
426    /// The transaction uses Snapshot isolation level by default.
427    ///
428    /// # Example
429    ///
430    /// ```ignore
431    /// store.transaction(|tx| {
432    ///     // Perform transactional operations
433    ///     Ok(())
434    /// })?;
435    /// ```
436    pub fn transaction<T, E>(
437        &self,
438        f: impl FnOnce(&mut crate::AcidTransaction) -> std::result::Result<T, E>,
439    ) -> std::result::Result<T, E>
440    where
441        E: From<OxirsError>,
442    {
443        // Ensure TransactionManager is initialized
444        self.ensure_tx_manager()?;
445
446        // Get the transaction manager
447        let mut tx_mgr_guard = self
448            .tx_manager
449            .write()
450            .map_err(|e| E::from(OxirsError::Store(format!("Failed to acquire lock: {e}"))))?;
451
452        let tx_mgr = tx_mgr_guard.as_mut().ok_or_else(|| {
453            E::from(OxirsError::Store(
454                "Transaction manager not initialized".to_string(),
455            ))
456        })?;
457
458        // Begin a transaction with Snapshot isolation
459        let mut transaction = tx_mgr.begin(IsolationLevel::Snapshot).map_err(E::from)?;
460
461        // Execute the user function
462        let result = f(&mut transaction);
463
464        // Commit the transaction if the function succeeded
465        match result {
466            Ok(value) => {
467                transaction.commit().map_err(E::from)?;
468                Ok(value)
469            }
470            Err(error) => {
471                let _ = transaction.abort();
472                Err(error)
473            }
474        }
475    }
476
477    /// Ensures the transaction manager is initialized
478    fn ensure_tx_manager(&self) -> Result<()> {
479        let mut tx_mgr_guard = self
480            .tx_manager
481            .write()
482            .map_err(|e| OxirsError::Store(format!("Failed to acquire lock: {e}")))?;
483
484        if tx_mgr_guard.is_none() {
485            // Determine WAL directory
486            let wal_dir = if let Some(ref wal_path) = self.wal_dir {
487                wal_path.clone()
488            } else {
489                // Use temporary directory for in-memory stores
490                std::env::temp_dir().join("oxirs_wal")
491            };
492
493            // Create the transaction manager
494            let tx_mgr = TransactionManager::new(&wal_dir)?;
495            *tx_mgr_guard = Some(tx_mgr);
496        }
497
498        Ok(())
499    }
500
501    /// Validates the store integrity
502    pub fn validate(&self) -> Result<()> {
503        // OxiRS doesn't have explicit validation yet
504        Ok(())
505    }
506
507    /// Optimizes the store layout
508    pub fn optimize(&self) -> Result<()> {
509        // Trigger arena cleanup if using ultra-performance mode
510        let store = self
511            .inner
512            .read()
513            .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
514        store.clear_arena();
515        Ok(())
516    }
517
518    /// Backs up the store to a path
519    pub fn backup<P: AsRef<Path>>(&self, path: P) -> Result<()> {
520        use crate::parser::RdfFormat;
521        use crate::serializer::Serializer;
522        use std::fs::File;
523        use std::io::Write;
524        use std::time::{SystemTime, UNIX_EPOCH};
525
526        let backup_path = path.as_ref();
527
528        // Create backup directory if it doesn't exist
529        if let Some(parent) = backup_path.parent() {
530            std::fs::create_dir_all(parent).map_err(|e| {
531                OxirsError::Store(format!("Failed to create backup directory: {e}"))
532            })?;
533        }
534
535        // Generate backup filename with timestamp
536        let timestamp = SystemTime::now()
537            .duration_since(UNIX_EPOCH)
538            .unwrap_or_default()
539            .as_secs();
540
541        let backup_file_path = if backup_path.is_dir() {
542            backup_path.join(format!("oxirs_backup_{timestamp}.nq"))
543        } else {
544            backup_path.to_path_buf()
545        };
546
547        // Get read lock on the store
548        let store = self
549            .inner
550            .read()
551            .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
552
553        // Get all quads from the store
554        let quads = store
555            .iter_quads()
556            .map_err(|e| OxirsError::Store(format!("Failed to iterate quads: {e}")))?;
557
558        // Create dataset from quads
559        let dataset = crate::model::dataset::Dataset::from_iter(quads.clone());
560
561        // Serialize to N-Quads format (most portable and complete format)
562        let serializer = Serializer::new(RdfFormat::NQuads);
563        let serialized_data = serializer
564            .serialize_dataset(&dataset)
565            .map_err(|e| OxirsError::Store(format!("Failed to serialize dataset: {e}")))?;
566
567        // Write to backup file
568        let mut backup_file = File::create(&backup_file_path)
569            .map_err(|e| OxirsError::Store(format!("Failed to create backup file: {e}")))?;
570
571        backup_file
572            .write_all(serialized_data.as_bytes())
573            .map_err(|e| OxirsError::Store(format!("Failed to write backup data: {e}")))?;
574
575        backup_file
576            .sync_all()
577            .map_err(|e| OxirsError::Store(format!("Failed to sync backup file: {e}")))?;
578
579        // Calculate backup size for logging
580        let backup_size = serialized_data.len();
581        let quad_count = quads.len();
582
583        tracing::info!(
584            "Store backup completed successfully. File: {}, Quads: {}, Size: {} bytes",
585            backup_file_path.display(),
586            quad_count,
587            backup_size
588        );
589
590        Ok(())
591    }
592
593    /// Flushes any pending changes to disk
594    pub fn flush(&self) -> Result<()> {
595        // No-op for now as OxiRS doesn't buffer writes
596        Ok(())
597    }
598}
599
600impl Default for Store {
601    fn default() -> Self {
602        Store::new().expect("Store::new() should not fail")
603    }
604}
605
606/// Iterator over quads (Oxigraph-compatible)
607pub struct QuadIter {
608    quads: Vec<Quad>,
609    index: usize,
610}
611
612impl Iterator for QuadIter {
613    type Item = Quad;
614
615    fn next(&mut self) -> Option<Self::Item> {
616        if self.index < self.quads.len() {
617            let quad = self.quads[self.index].clone();
618            self.index += 1;
619            Some(quad)
620        } else {
621            None
622        }
623    }
624}
625
626/// Iterator over graph names (Oxigraph-compatible)
627pub struct GraphNameIter {
628    graphs: Vec<NamedNode>,
629    index: usize,
630}
631
632impl Iterator for GraphNameIter {
633    type Item = NamedNode;
634
635    fn next(&mut self) -> Option<Self::Item> {
636        if self.index < self.graphs.len() {
637            let graph = self.graphs[self.index].clone();
638            self.index += 1;
639            Some(graph)
640        } else {
641            None
642        }
643    }
644}
645
646/// Oxigraph-compatible query results
647pub struct QueryResults {
648    #[allow(dead_code)]
649    inner: OxirsQueryResults,
650}
651
652impl QueryResults {
653    /// Returns true if the results are a boolean
654    pub fn is_boolean(&self) -> bool {
655        matches!(
656            self.inner.results(),
657            crate::rdf_store::types::QueryResults::Boolean(_)
658        )
659    }
660
661    /// Returns the boolean value if the results are a boolean
662    pub fn boolean(&self) -> Option<bool> {
663        match self.inner.results() {
664            crate::rdf_store::types::QueryResults::Boolean(b) => Some(*b),
665            _ => None,
666        }
667    }
668
669    /// Returns true if the results are solutions
670    pub fn is_solutions(&self) -> bool {
671        matches!(
672            self.inner.results(),
673            crate::rdf_store::types::QueryResults::Bindings(_)
674        )
675    }
676
677    /// Returns true if the results are a graph
678    pub fn is_graph(&self) -> bool {
679        matches!(
680            self.inner.results(),
681            crate::rdf_store::types::QueryResults::Graph(_)
682        )
683    }
684}
685
686/// Oxigraph-compatible transaction
687///
688/// Note: This is a placeholder implementation. Full transactional support
689/// would require implementing proper transaction isolation in OxiRS.
690pub struct Transaction {
691    // Placeholder for future transaction implementation
692    operations: Vec<TransactionOp>,
693}
694
695enum TransactionOp {
696    #[allow(dead_code)]
697    Insert(Quad),
698    #[allow(dead_code)]
699    Remove(Quad),
700}
701
702impl Transaction {
703    #[allow(dead_code)]
704    fn new() -> Self {
705        Transaction {
706            operations: Vec::new(),
707        }
708    }
709
710    /// Inserts a quad in the transaction
711    pub fn insert<'b>(&mut self, quad: impl Into<QuadRef<'b>>) -> Result<bool> {
712        let quad_ref = quad.into();
713        let quad = Quad::new(
714            quad_ref.subject().to_owned(),
715            quad_ref.predicate().to_owned(),
716            quad_ref.object().to_owned(),
717            quad_ref.graph_name().to_owned(),
718        );
719        self.operations.push(TransactionOp::Insert(quad));
720        Ok(true) // Optimistically return true
721    }
722
723    /// Removes a quad in the transaction
724    pub fn remove<'b>(&mut self, quad: impl Into<QuadRef<'b>>) -> Result<bool> {
725        let quad_ref = quad.into();
726        let quad = Quad::new(
727            quad_ref.subject().to_owned(),
728            quad_ref.predicate().to_owned(),
729            quad_ref.object().to_owned(),
730            quad_ref.graph_name().to_owned(),
731        );
732        self.operations.push(TransactionOp::Remove(quad));
733        Ok(true) // Optimistically return true
734    }
735}
736
737/// Oxigraph-compatible error type
738#[derive(Debug, thiserror::Error)]
739pub enum OxigraphCompatError {
740    #[error("Store error: {0}")]
741    Store(String),
742    #[error("Parse error: {0}")]
743    Parse(String),
744    #[error("IO error: {0}")]
745    Io(#[from] std::io::Error),
746}
747
748impl From<OxirsError> for OxigraphCompatError {
749    fn from(err: OxirsError) -> Self {
750        match err {
751            OxirsError::Store(msg) => OxigraphCompatError::Store(msg),
752            OxirsError::Parse(msg) => OxigraphCompatError::Parse(msg),
753            _ => OxigraphCompatError::Store(err.to_string()),
754        }
755    }
756}
757
758#[cfg(test)]
759mod tests {
760    use super::*;
761    use crate::model::{Literal, NamedNode};
762    use crate::parser::RdfFormat;
763    use std::io::Cursor;
764
765    #[test]
766    fn test_oxigraph_compat_store_creation() {
767        let store = Store::new().unwrap();
768        assert!(store.is_empty().unwrap());
769        assert_eq!(store.len().unwrap(), 0);
770    }
771
772    #[test]
773    fn test_oxigraph_compat_insert_and_query() {
774        let store = Store::new().unwrap();
775
776        // Create test quad
777        let subject = NamedNode::new("http://example.org/subject").unwrap();
778        let predicate = NamedNode::new("http://example.org/predicate").unwrap();
779        let object = Literal::new("test object");
780        let graph = NamedNode::new("http://example.org/graph").unwrap();
781
782        let quad = Quad::new(
783            subject.clone(),
784            predicate.clone(),
785            object.clone(),
786            graph.clone(),
787        );
788
789        // Insert quad
790        assert!(store.insert(QuadRef::from(&quad)).unwrap());
791        assert_eq!(store.len().unwrap(), 1);
792        assert!(!store.is_empty().unwrap());
793
794        // Check contains
795        assert!(store.contains(QuadRef::from(&quad)).unwrap());
796
797        // Query by pattern
798        let quads: Vec<_> = store
799            .quads_for_pattern(
800                Some(SubjectRef::NamedNode(&subject)),
801                None::<PredicateRef>,
802                None::<ObjectRef>,
803                None::<GraphNameRef>,
804            )
805            .collect();
806        assert_eq!(quads.len(), 1);
807        assert_eq!(quads[0], quad);
808
809        // Remove quad
810        assert!(store.remove(QuadRef::from(&quad)).unwrap());
811        assert!(store.is_empty().unwrap());
812    }
813
814    #[test]
815    fn test_oxigraph_compat_extend() {
816        let store = Store::new().unwrap();
817
818        let quads = [
819            Quad::new(
820                NamedNode::new("http://example.org/s1").unwrap(),
821                NamedNode::new("http://example.org/p1").unwrap(),
822                Literal::new("o1"),
823                GraphName::DefaultGraph,
824            ),
825            Quad::new(
826                NamedNode::new("http://example.org/s2").unwrap(),
827                NamedNode::new("http://example.org/p2").unwrap(),
828                Literal::new("o2"),
829                NamedNode::new("http://example.org/g1").unwrap(),
830            ),
831        ];
832
833        store.extend(quads.iter().map(QuadRef::from)).unwrap();
834        assert_eq!(store.len().unwrap(), 2);
835    }
836
837    #[test]
838    fn test_oxigraph_compat_named_graphs() {
839        let store = Store::new().unwrap();
840
841        // Create nodes
842        let s1 = NamedNode::new("http://example.org/s1").unwrap();
843        let s2 = NamedNode::new("http://example.org/s2").unwrap();
844        let p1 = NamedNode::new("http://example.org/p1").unwrap();
845        let p2 = NamedNode::new("http://example.org/p2").unwrap();
846        let o1 = Literal::new("o1");
847        let o2 = Literal::new("o2");
848        let g1 = NamedNode::new("http://example.org/g1").unwrap();
849        let g2 = NamedNode::new("http://example.org/g2").unwrap();
850
851        // Insert quads in different graphs
852        store
853            .insert(QuadRef::new(
854                SubjectRef::NamedNode(&s1),
855                PredicateRef::NamedNode(&p1),
856                ObjectRef::Literal(&o1),
857                GraphNameRef::NamedNode(&g1),
858            ))
859            .unwrap();
860
861        store
862            .insert(QuadRef::new(
863                SubjectRef::NamedNode(&s2),
864                PredicateRef::NamedNode(&p2),
865                ObjectRef::Literal(&o2),
866                GraphNameRef::NamedNode(&g2),
867            ))
868            .unwrap();
869
870        // Check named graphs
871        let graphs: Vec<_> = store.named_graphs().collect();
872        assert_eq!(graphs.len(), 2);
873        assert!(graphs.contains(&g1));
874        assert!(graphs.contains(&g2));
875
876        // Check contains_named_graph
877        assert!(store
878            .contains_named_graph(NamedOrBlankNodeRef::NamedNode(&g1))
879            .unwrap());
880        assert!(store
881            .contains_named_graph(NamedOrBlankNodeRef::NamedNode(&g2))
882            .unwrap());
883    }
884
885    #[test]
886    fn test_oxigraph_compat_clear_graph() {
887        let store = Store::new().unwrap();
888
889        // Create nodes
890        let s1 = NamedNode::new("http://example.org/s1").unwrap();
891        let s2 = NamedNode::new("http://example.org/s2").unwrap();
892        let p1 = NamedNode::new("http://example.org/p1").unwrap();
893        let p2 = NamedNode::new("http://example.org/p2").unwrap();
894        let o1 = Literal::new("o1");
895        let o2 = Literal::new("o2");
896        let graph = NamedNode::new("http://example.org/graph").unwrap();
897
898        // Add quads to specific graph and default graph
899        store
900            .insert(QuadRef::new(
901                SubjectRef::NamedNode(&s1),
902                PredicateRef::NamedNode(&p1),
903                ObjectRef::Literal(&o1),
904                GraphNameRef::NamedNode(&graph),
905            ))
906            .unwrap();
907
908        store
909            .insert(QuadRef::new(
910                SubjectRef::NamedNode(&s2),
911                PredicateRef::NamedNode(&p2),
912                ObjectRef::Literal(&o2),
913                GraphNameRef::DefaultGraph,
914            ))
915            .unwrap();
916
917        assert_eq!(store.len().unwrap(), 2);
918
919        // Clear specific graph
920        store.clear_graph(GraphNameRef::NamedNode(&graph)).unwrap();
921        assert_eq!(store.len().unwrap(), 1); // Only default graph quad remains
922
923        // Clear all
924        store.clear().unwrap();
925        assert!(store.is_empty().unwrap());
926    }
927
928    #[test]
929    fn test_oxigraph_compat_load_from_reader() {
930        let store = Store::new().unwrap();
931
932        let turtle_data = r#"
933            @prefix ex: <http://example.org/> .
934            ex:subject ex:predicate "object" .
935        "#;
936
937        let reader = Cursor::new(turtle_data.as_bytes());
938        store
939            .load_from_reader(
940                reader,
941                RdfFormat::Turtle,
942                Some("http://example.org/"),
943                None::<GraphName>,
944            )
945            .unwrap();
946
947        assert_eq!(store.len().unwrap(), 1);
948
949        // Verify the loaded data
950        let quads: Vec<_> = store.iter().collect();
951        assert_eq!(quads.len(), 1);
952        assert_eq!(
953            quads[0].subject().to_string(),
954            "<http://example.org/subject>"
955        );
956        assert_eq!(
957            quads[0].predicate().to_string(),
958            "<http://example.org/predicate>"
959        );
960    }
961
962    #[test]
963    fn test_oxigraph_compat_dump_to_writer() {
964        let store = Store::new().unwrap();
965
966        // Create nodes
967        let subject = NamedNode::new("http://example.org/subject").unwrap();
968        let predicate = NamedNode::new("http://example.org/predicate").unwrap();
969        let object = Literal::new("object");
970
971        // Add some test data
972        store
973            .insert(QuadRef::new(
974                SubjectRef::NamedNode(&subject),
975                PredicateRef::NamedNode(&predicate),
976                ObjectRef::Literal(&object),
977                GraphNameRef::DefaultGraph,
978            ))
979            .unwrap();
980
981        // Dump to N-Triples format
982        let mut output = Vec::new();
983        store
984            .dump_to_writer(&mut output, RdfFormat::NTriples, None::<GraphNameRef>)
985            .unwrap();
986
987        let result = String::from_utf8(output).unwrap();
988        assert!(result.contains("<http://example.org/subject>"));
989        assert!(result.contains("<http://example.org/predicate>"));
990        assert!(result.contains("\"object\""));
991    }
992}