1use 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
17pub struct Store {
23 inner: Arc<RwLock<RdfStore>>,
24 tx_manager: Arc<RwLock<Option<TransactionManager>>>,
25 wal_dir: Option<PathBuf>,
26}
27
28impl Store {
29 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 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 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 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 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 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 let mut data = String::new();
128 let mut reader = reader;
129 reader
131 .read_to_string(&mut data)
132 .map_err(|e| OxirsError::Parse(format!("Failed to read input: {e}")))?;
133
134 let mut parser = Parser::new(format);
136 if let Some(base) = base_iri {
137 parser = parser.with_base_iri(base);
138 }
139
140 let quads = parser.parse_str_to_quads(&data)?;
142
143 let store = self
145 .inner
146 .write()
147 .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
148
149 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 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 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 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 let output = match format {
197 RdfFormat::Turtle | RdfFormat::NTriples | RdfFormat::RdfXml => {
198 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 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 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 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 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 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 s_ref.to_owned()
267 });
268
269 let predicate = predicate.map(|p| {
270 let p_ref = p.into();
271 match p_ref {
272 PredicateRef::NamedNode(n) => Predicate::NamedNode(n.to_owned()),
273 PredicateRef::Variable(v) => Predicate::Variable(v.to_owned()),
274 }
275 });
276
277 let object = object.map(|o| {
278 let o_ref = o.into();
279 o_ref.to_owned()
280 });
281
282 let graph_name = graph_name.map(|g| {
283 let g_ref = g.into();
284 g_ref.to_owned()
285 });
286
287 let quads = match self.inner.read() {
289 Ok(store) => store
290 .query_quads(
291 subject.as_ref(),
292 predicate.as_ref(),
293 object.as_ref(),
294 graph_name.as_ref(),
295 )
296 .unwrap_or_default(),
297 _ => Vec::new(),
298 };
299
300 QuadIter { quads, index: 0 }
301 }
302
303 pub fn iter(&self) -> QuadIter {
305 self.quads_for_pattern(
306 None::<SubjectRef>,
307 None::<PredicateRef>,
308 None::<ObjectRef>,
309 None::<GraphNameRef>,
310 )
311 }
312
313 pub fn named_graphs(&self) -> GraphNameIter {
315 let mut graph_names = std::collections::HashSet::new();
317 if let Ok(store) = self.inner.read() {
318 if let Ok(quads) = store.iter_quads() {
319 for quad in quads {
320 if let GraphName::NamedNode(n) = quad.graph_name() {
321 graph_names.insert(n.clone());
322 }
323 }
324 }
325 }
326
327 GraphNameIter {
328 graphs: graph_names.into_iter().collect(),
329 index: 0,
330 }
331 }
332
333 pub fn contains_named_graph<'a>(
335 &self,
336 graph_name: impl Into<NamedOrBlankNodeRef<'a>>,
337 ) -> Result<bool> {
338 let graph_ref = graph_name.into();
339 let graph = match graph_ref {
340 NamedOrBlankNodeRef::NamedNode(n) => GraphName::NamedNode(n.to_owned()),
341 NamedOrBlankNodeRef::BlankNode(b) => GraphName::BlankNode(b.to_owned()),
342 };
343
344 let store = self
346 .inner
347 .read()
348 .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
349 let quads = store.query_quads(None, None, None, Some(&graph))?;
350 Ok(!quads.is_empty())
351 }
352
353 pub fn clear(&self) -> Result<()> {
355 let mut store = self
356 .inner
357 .write()
358 .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
359 store.clear()
360 }
361
362 pub fn clear_graph<'a>(&self, graph_name: impl Into<GraphNameRef<'a>>) -> Result<()> {
364 let graph_ref = graph_name.into();
365 let graph = graph_ref.to_owned();
366
367 let store = self
368 .inner
369 .write()
370 .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
371
372 let quads_to_remove = store.query_quads(None, None, None, Some(&graph))?;
374
375 for quad in quads_to_remove {
377 store.remove_quad(&quad)?;
378 }
379
380 Ok(())
381 }
382
383 pub fn query(&self, query: &str) -> Result<QueryResults> {
385 let store = self
386 .inner
387 .read()
388 .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
389 let results = store.query(query)?;
390 Ok(QueryResults { inner: results })
391 }
392
393 pub fn update(&self, update_str: &str) -> Result<()> {
395 use crate::query::{UpdateExecutor, UpdateParser};
396
397 let parser = UpdateParser::new();
399 let update = parser.parse(update_str)?;
400
401 let store = self
403 .inner
404 .write()
405 .map_err(|e| OxirsError::Store(format!("Failed to acquire write lock: {e}")))?;
406
407 let executor = UpdateExecutor::new(&*store);
409 executor.execute(&update)?;
410
411 Ok(())
412 }
413
414 pub fn transaction<T, E>(
428 &self,
429 f: impl FnOnce(&mut crate::AcidTransaction) -> std::result::Result<T, E>,
430 ) -> std::result::Result<T, E>
431 where
432 E: From<OxirsError>,
433 {
434 self.ensure_tx_manager()?;
436
437 let mut tx_mgr_guard = self
439 .tx_manager
440 .write()
441 .map_err(|e| E::from(OxirsError::Store(format!("Failed to acquire lock: {e}"))))?;
442
443 let tx_mgr = tx_mgr_guard.as_mut().ok_or_else(|| {
444 E::from(OxirsError::Store(
445 "Transaction manager not initialized".to_string(),
446 ))
447 })?;
448
449 let mut transaction = tx_mgr.begin(IsolationLevel::Snapshot).map_err(E::from)?;
451
452 let result = f(&mut transaction);
454
455 match result {
457 Ok(value) => {
458 let inserts = transaction.pending_inserts().to_vec();
461 let deletes = transaction.pending_deletes().to_vec();
462
463 transaction.commit().map_err(E::from)?;
465
466 let store = self.inner.write().map_err(|e| {
470 E::from(OxirsError::Store(format!(
471 "Failed to acquire write lock: {e}"
472 )))
473 })?;
474 for quad in &deletes {
475 store.remove_quad(quad).map_err(E::from)?;
476 }
477 for quad in inserts {
478 store.insert_quad(quad).map_err(E::from)?;
479 }
480 Ok(value)
481 }
482 Err(error) => {
483 let _ = transaction.abort();
484 Err(error)
485 }
486 }
487 }
488
489 fn ensure_tx_manager(&self) -> Result<()> {
491 let mut tx_mgr_guard = self
492 .tx_manager
493 .write()
494 .map_err(|e| OxirsError::Store(format!("Failed to acquire lock: {e}")))?;
495
496 if tx_mgr_guard.is_none() {
497 let wal_dir = if let Some(ref wal_path) = self.wal_dir {
499 wal_path.clone()
500 } else {
501 std::env::temp_dir().join(format!("oxirs_wal_{}", uuid::Uuid::new_v4()))
505 };
506
507 let tx_mgr = TransactionManager::new(&wal_dir)?;
509 *tx_mgr_guard = Some(tx_mgr);
510 }
511
512 Ok(())
513 }
514
515 pub fn validate(&self) -> Result<()> {
521 let store = self
522 .inner
523 .read()
524 .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
525
526 let reported = OxirsStoreTrait::len(&*store)?;
527 let materialized = OxirsStoreTrait::quads(&*store)?.len();
528 if reported != materialized {
529 return Err(OxirsError::Store(format!(
530 "Store consistency check failed: len() reports {reported} quads but {materialized} were found in the indexes"
531 )));
532 }
533
534 Ok(())
535 }
536
537 pub fn optimize(&self) -> Result<()> {
539 let store = self
541 .inner
542 .read()
543 .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
544 store.clear_arena();
545 Ok(())
546 }
547
548 pub fn backup<P: AsRef<Path>>(&self, path: P) -> Result<()> {
550 use crate::parser::RdfFormat;
551 use crate::serializer::Serializer;
552 use std::fs::File;
553 use std::io::Write;
554 use std::time::{SystemTime, UNIX_EPOCH};
555
556 let backup_path = path.as_ref();
557
558 if let Some(parent) = backup_path.parent() {
560 std::fs::create_dir_all(parent).map_err(|e| {
561 OxirsError::Store(format!("Failed to create backup directory: {e}"))
562 })?;
563 }
564
565 let timestamp = SystemTime::now()
567 .duration_since(UNIX_EPOCH)
568 .unwrap_or_default()
569 .as_secs();
570
571 let backup_file_path = if backup_path.is_dir() {
572 backup_path.join(format!("oxirs_backup_{timestamp}.nq"))
573 } else {
574 backup_path.to_path_buf()
575 };
576
577 let store = self
579 .inner
580 .read()
581 .map_err(|e| OxirsError::Store(format!("Failed to acquire read lock: {e}")))?;
582
583 let quads = store
585 .iter_quads()
586 .map_err(|e| OxirsError::Store(format!("Failed to iterate quads: {e}")))?;
587
588 let dataset = crate::model::dataset::Dataset::from_iter(quads.clone());
590
591 let serializer = Serializer::new(RdfFormat::NQuads);
593 let serialized_data = serializer
594 .serialize_dataset(&dataset)
595 .map_err(|e| OxirsError::Store(format!("Failed to serialize dataset: {e}")))?;
596
597 let mut backup_file = File::create(&backup_file_path)
599 .map_err(|e| OxirsError::Store(format!("Failed to create backup file: {e}")))?;
600
601 backup_file
602 .write_all(serialized_data.as_bytes())
603 .map_err(|e| OxirsError::Store(format!("Failed to write backup data: {e}")))?;
604
605 backup_file
606 .sync_all()
607 .map_err(|e| OxirsError::Store(format!("Failed to sync backup file: {e}")))?;
608
609 let backup_size = serialized_data.len();
611 let quad_count = quads.len();
612
613 tracing::info!(
614 "Store backup completed successfully. File: {}, Quads: {}, Size: {} bytes",
615 backup_file_path.display(),
616 quad_count,
617 backup_size
618 );
619
620 Ok(())
621 }
622
623 pub fn flush(&self) -> Result<()> {
630 let store = self.inner.read().map_err(|e| {
631 OxirsError::Store(format!("Failed to acquire read lock for flush: {e}"))
632 })?;
633 store.flush()
634 }
635}
636
637impl Default for Store {
638 fn default() -> Self {
639 Store::new().expect("Store::new() should not fail")
640 }
641}
642
643pub struct QuadIter {
645 quads: Vec<Quad>,
646 index: usize,
647}
648
649impl Iterator for QuadIter {
650 type Item = Quad;
651
652 fn next(&mut self) -> Option<Self::Item> {
653 if self.index < self.quads.len() {
654 let quad = self.quads[self.index].clone();
655 self.index += 1;
656 Some(quad)
657 } else {
658 None
659 }
660 }
661}
662
663pub struct GraphNameIter {
665 graphs: Vec<NamedNode>,
666 index: usize,
667}
668
669impl Iterator for GraphNameIter {
670 type Item = NamedNode;
671
672 fn next(&mut self) -> Option<Self::Item> {
673 if self.index < self.graphs.len() {
674 let graph = self.graphs[self.index].clone();
675 self.index += 1;
676 Some(graph)
677 } else {
678 None
679 }
680 }
681}
682
683pub struct QueryResults {
685 #[allow(dead_code)]
686 inner: OxirsQueryResults,
687}
688
689impl QueryResults {
690 pub fn is_boolean(&self) -> bool {
692 matches!(
693 self.inner.results(),
694 crate::rdf_store::types::QueryResults::Boolean(_)
695 )
696 }
697
698 pub fn boolean(&self) -> Option<bool> {
700 match self.inner.results() {
701 crate::rdf_store::types::QueryResults::Boolean(b) => Some(*b),
702 _ => None,
703 }
704 }
705
706 pub fn is_solutions(&self) -> bool {
708 matches!(
709 self.inner.results(),
710 crate::rdf_store::types::QueryResults::Bindings(_)
711 )
712 }
713
714 pub fn is_graph(&self) -> bool {
716 matches!(
717 self.inner.results(),
718 crate::rdf_store::types::QueryResults::Graph(_)
719 )
720 }
721}
722
723pub struct Transaction {
728 operations: Vec<TransactionOp>,
730}
731
732enum TransactionOp {
733 #[allow(dead_code)]
734 Insert(Quad),
735 #[allow(dead_code)]
736 Remove(Quad),
737}
738
739impl Transaction {
740 #[allow(dead_code)]
741 fn new() -> Self {
742 Transaction {
743 operations: Vec::new(),
744 }
745 }
746
747 pub fn insert<'b>(&mut self, quad: impl Into<QuadRef<'b>>) -> Result<bool> {
749 let quad_ref = quad.into();
750 let quad = Quad::new(
751 quad_ref.subject().to_owned(),
752 quad_ref.predicate().to_owned(),
753 quad_ref.object().to_owned(),
754 quad_ref.graph_name().to_owned(),
755 );
756 self.operations.push(TransactionOp::Insert(quad));
757 Ok(true) }
759
760 pub fn remove<'b>(&mut self, quad: impl Into<QuadRef<'b>>) -> Result<bool> {
762 let quad_ref = quad.into();
763 let quad = Quad::new(
764 quad_ref.subject().to_owned(),
765 quad_ref.predicate().to_owned(),
766 quad_ref.object().to_owned(),
767 quad_ref.graph_name().to_owned(),
768 );
769 self.operations.push(TransactionOp::Remove(quad));
770 Ok(true) }
772}
773
774#[derive(Debug, thiserror::Error)]
776pub enum OxigraphCompatError {
777 #[error("Store error: {0}")]
778 Store(String),
779 #[error("Parse error: {0}")]
780 Parse(String),
781 #[error("IO error: {0}")]
782 Io(#[from] std::io::Error),
783}
784
785impl From<OxirsError> for OxigraphCompatError {
786 fn from(err: OxirsError) -> Self {
787 match err {
788 OxirsError::Store(msg) => OxigraphCompatError::Store(msg),
789 OxirsError::Parse(msg) => OxigraphCompatError::Parse(msg),
790 _ => OxigraphCompatError::Store(err.to_string()),
791 }
792 }
793}
794
795#[cfg(test)]
796mod tests {
797 use super::*;
798 use crate::model::{Literal, NamedNode};
799 use crate::parser::RdfFormat;
800 use std::io::Cursor;
801
802 #[test]
803 fn test_oxigraph_compat_store_creation() {
804 let store = Store::new().expect("construction should succeed");
805 assert!(store.is_empty().expect("store operation should succeed"));
806 assert_eq!(store.len().expect("store operation should succeed"), 0);
807 }
808
809 #[test]
810 fn test_oxigraph_compat_insert_and_query() {
811 let store = Store::new().expect("construction should succeed");
812
813 let subject = NamedNode::new("http://example.org/subject").expect("valid IRI");
815 let predicate = NamedNode::new("http://example.org/predicate").expect("valid IRI");
816 let object = Literal::new("test object");
817 let graph = NamedNode::new("http://example.org/graph").expect("valid IRI");
818
819 let quad = Quad::new(
820 subject.clone(),
821 predicate.clone(),
822 object.clone(),
823 graph.clone(),
824 );
825
826 assert!(store
828 .insert(QuadRef::from(&quad))
829 .expect("store insert should succeed"));
830 assert_eq!(store.len().expect("store operation should succeed"), 1);
831 assert!(!store.is_empty().expect("store operation should succeed"));
832
833 assert!(store
835 .contains(QuadRef::from(&quad))
836 .expect("store contains should succeed"));
837
838 let quads: Vec<_> = store
840 .quads_for_pattern(
841 Some(SubjectRef::NamedNode(&subject)),
842 None::<PredicateRef>,
843 None::<ObjectRef>,
844 None::<GraphNameRef>,
845 )
846 .collect();
847 assert_eq!(quads.len(), 1);
848 assert_eq!(quads[0], quad);
849
850 assert!(store
852 .remove(QuadRef::from(&quad))
853 .expect("store remove should succeed"));
854 assert!(store.is_empty().expect("store operation should succeed"));
855 }
856
857 #[test]
858 fn test_oxigraph_compat_extend() {
859 let store = Store::new().expect("construction should succeed");
860
861 let quads = [
862 Quad::new(
863 NamedNode::new("http://example.org/s1").expect("valid IRI"),
864 NamedNode::new("http://example.org/p1").expect("valid IRI"),
865 Literal::new("o1"),
866 GraphName::DefaultGraph,
867 ),
868 Quad::new(
869 NamedNode::new("http://example.org/s2").expect("valid IRI"),
870 NamedNode::new("http://example.org/p2").expect("valid IRI"),
871 Literal::new("o2"),
872 NamedNode::new("http://example.org/g1").expect("valid IRI"),
873 ),
874 ];
875
876 store
877 .extend(quads.iter().map(QuadRef::from))
878 .expect("extend should succeed");
879 assert_eq!(store.len().expect("store operation should succeed"), 2);
880 }
881
882 #[test]
883 fn test_oxigraph_compat_named_graphs() {
884 let store = Store::new().expect("construction should succeed");
885
886 let s1 = NamedNode::new("http://example.org/s1").expect("valid IRI");
888 let s2 = NamedNode::new("http://example.org/s2").expect("valid IRI");
889 let p1 = NamedNode::new("http://example.org/p1").expect("valid IRI");
890 let p2 = NamedNode::new("http://example.org/p2").expect("valid IRI");
891 let o1 = Literal::new("o1");
892 let o2 = Literal::new("o2");
893 let g1 = NamedNode::new("http://example.org/g1").expect("valid IRI");
894 let g2 = NamedNode::new("http://example.org/g2").expect("valid IRI");
895
896 store
898 .insert(QuadRef::new(
899 SubjectRef::NamedNode(&s1),
900 PredicateRef::NamedNode(&p1),
901 ObjectRef::Literal(&o1),
902 GraphNameRef::NamedNode(&g1),
903 ))
904 .expect("operation should succeed");
905
906 store
907 .insert(QuadRef::new(
908 SubjectRef::NamedNode(&s2),
909 PredicateRef::NamedNode(&p2),
910 ObjectRef::Literal(&o2),
911 GraphNameRef::NamedNode(&g2),
912 ))
913 .expect("operation should succeed");
914
915 let graphs: Vec<_> = store.named_graphs().collect();
917 assert_eq!(graphs.len(), 2);
918 assert!(graphs.contains(&g1));
919 assert!(graphs.contains(&g2));
920
921 assert!(store
923 .contains_named_graph(NamedOrBlankNodeRef::NamedNode(&g1))
924 .expect("operation should succeed"));
925 assert!(store
926 .contains_named_graph(NamedOrBlankNodeRef::NamedNode(&g2))
927 .expect("operation should succeed"));
928 }
929
930 #[test]
931 fn test_oxigraph_compat_clear_graph() {
932 let store = Store::new().expect("construction should succeed");
933
934 let s1 = NamedNode::new("http://example.org/s1").expect("valid IRI");
936 let s2 = NamedNode::new("http://example.org/s2").expect("valid IRI");
937 let p1 = NamedNode::new("http://example.org/p1").expect("valid IRI");
938 let p2 = NamedNode::new("http://example.org/p2").expect("valid IRI");
939 let o1 = Literal::new("o1");
940 let o2 = Literal::new("o2");
941 let graph = NamedNode::new("http://example.org/graph").expect("valid IRI");
942
943 store
945 .insert(QuadRef::new(
946 SubjectRef::NamedNode(&s1),
947 PredicateRef::NamedNode(&p1),
948 ObjectRef::Literal(&o1),
949 GraphNameRef::NamedNode(&graph),
950 ))
951 .expect("operation should succeed");
952
953 store
954 .insert(QuadRef::new(
955 SubjectRef::NamedNode(&s2),
956 PredicateRef::NamedNode(&p2),
957 ObjectRef::Literal(&o2),
958 GraphNameRef::DefaultGraph,
959 ))
960 .expect("operation should succeed");
961
962 assert_eq!(store.len().expect("store operation should succeed"), 2);
963
964 store
966 .clear_graph(GraphNameRef::NamedNode(&graph))
967 .expect("clear_graph should succeed");
968 assert_eq!(store.len().expect("store operation should succeed"), 1); store.clear().expect("store operation should succeed");
972 assert!(store.is_empty().expect("store operation should succeed"));
973 }
974
975 #[test]
976 fn test_oxigraph_compat_load_from_reader() {
977 let store = Store::new().expect("construction should succeed");
978
979 let turtle_data = r#"
980 @prefix ex: <http://example.org/> .
981 ex:subject ex:predicate "object" .
982 "#;
983
984 let reader = Cursor::new(turtle_data.as_bytes());
985 store
986 .load_from_reader(
987 reader,
988 RdfFormat::Turtle,
989 Some("http://example.org/"),
990 None::<GraphName>,
991 )
992 .expect("operation should succeed");
993
994 assert_eq!(store.len().expect("store operation should succeed"), 1);
995
996 let quads: Vec<_> = store.iter().collect();
998 assert_eq!(quads.len(), 1);
999 assert_eq!(
1000 quads[0].subject().to_string(),
1001 "<http://example.org/subject>"
1002 );
1003 assert_eq!(
1004 quads[0].predicate().to_string(),
1005 "<http://example.org/predicate>"
1006 );
1007 }
1008
1009 #[test]
1010 fn test_oxigraph_compat_dump_to_writer() {
1011 let store = Store::new().expect("construction should succeed");
1012
1013 let subject = NamedNode::new("http://example.org/subject").expect("valid IRI");
1015 let predicate = NamedNode::new("http://example.org/predicate").expect("valid IRI");
1016 let object = Literal::new("object");
1017
1018 store
1020 .insert(QuadRef::new(
1021 SubjectRef::NamedNode(&subject),
1022 PredicateRef::NamedNode(&predicate),
1023 ObjectRef::Literal(&object),
1024 GraphNameRef::DefaultGraph,
1025 ))
1026 .expect("operation should succeed");
1027
1028 let mut output = Vec::new();
1030 store
1031 .dump_to_writer(&mut output, RdfFormat::NTriples, None::<GraphNameRef>)
1032 .expect("operation should succeed");
1033
1034 let result = String::from_utf8(output).expect("bytes should be valid UTF-8");
1035 assert!(result.contains("<http://example.org/subject>"));
1036 assert!(result.contains("<http://example.org/predicate>"));
1037 assert!(result.contains("\"object\""));
1038 }
1039
1040 #[test]
1041 fn regression_flush_persists_to_disk() {
1042 let dir =
1045 std::env::temp_dir().join(format!("oxirs_flush_regression_{}", uuid::Uuid::new_v4()));
1046
1047 let subject = NamedNode::new("http://example.org/s").expect("valid IRI");
1048 let predicate = NamedNode::new("http://example.org/p").expect("valid IRI");
1049 let object = Literal::new("durable");
1050 let quad = Quad::new(
1051 subject.clone(),
1052 predicate.clone(),
1053 object.clone(),
1054 GraphName::DefaultGraph,
1055 );
1056
1057 {
1058 let store = Store::open(&dir).expect("open persistent store");
1059 store
1060 .insert(QuadRef::from(&quad))
1061 .expect("insert should succeed");
1062 store.flush().expect("flush should succeed and be durable");
1064 }
1065
1066 {
1068 let reopened = Store::open(&dir).expect("reopen persistent store");
1069 assert!(
1070 reopened
1071 .contains(QuadRef::from(&quad))
1072 .expect("contains should succeed"),
1073 "flushed quad was lost after reopen"
1074 );
1075 }
1076
1077 let _ = std::fs::remove_dir_all(&dir);
1078 }
1079
1080 #[test]
1081 fn regression_flush_in_memory_is_ok() {
1082 let store = Store::new().expect("construction should succeed");
1085 store.flush().expect("in-memory flush should be Ok");
1086 }
1087}