Skip to main content

fukurow_core/
store.rs

1//! Graph storage and manipulation
2
3use crate::model::{Triple, NamedGraph, JsonLdDocument};
4use std::collections::HashMap;
5use anyhow::Result;
6use smallvec::SmallVec;
7
8/// In-memory graph store with indexing for fast queries
9#[derive(Debug, Clone, Default)]
10pub struct GraphStore {
11    /// Named graphs for organization
12    graphs: HashMap<String, NamedGraph>,
13    /// Default graph for unnamed triples
14    default_graph: NamedGraph,
15    /// Subject index: subject -> list of triple indices in default_graph.triples
16    subject_index: HashMap<String, SmallVec<[usize; 8]>>,
17    /// Predicate index: predicate -> list of triple indices
18    predicate_index: HashMap<String, SmallVec<[usize; 8]>>,
19    /// Object index: object -> list of triple indices
20    object_index: HashMap<String, SmallVec<[usize; 8]>>,
21}
22
23impl GraphStore {
24    pub fn new() -> Self {
25        Self {
26            graphs: HashMap::new(),
27            default_graph: NamedGraph {
28                name: "default".to_string(),
29                triples: Vec::new(),
30            },
31            subject_index: HashMap::new(),
32            predicate_index: HashMap::new(),
33            object_index: HashMap::new(),
34        }
35    }
36
37    /// Add a triple to the default graph
38    pub fn add_triple(&mut self, triple: Triple) {
39        let index = self.default_graph.triples.len();
40        self.default_graph.triples.push(triple.clone());
41
42        // Update indices
43        self.subject_index.entry(triple.subject.clone()).or_insert_with(SmallVec::new).push(index);
44        self.predicate_index.entry(triple.predicate.clone()).or_insert_with(SmallVec::new).push(index);
45        self.object_index.entry(triple.object.clone()).or_insert_with(SmallVec::new).push(index);
46    }
47
48    /// Add a triple to a named graph
49    pub fn add_triple_to_graph(&mut self, graph_name: &str, triple: Triple) {
50        self.graphs
51            .entry(graph_name.to_string())
52            .or_insert_with(|| NamedGraph {
53                name: graph_name.to_string(),
54                triples: Vec::new(),
55            })
56            .triples
57            .push(triple);
58    }
59
60    /// Add multiple triples to a named graph
61    pub fn add_triples_to_graph(&mut self, graph_name: &str, triples: Vec<Triple>) {
62        self.graphs
63            .entry(graph_name.to_string())
64            .or_insert_with(|| NamedGraph {
65                name: graph_name.to_string(),
66                triples: Vec::new(),
67            })
68            .triples
69            .extend(triples);
70    }
71
72    /// Get all triples from default graph
73    pub fn get_default_graph(&self) -> &NamedGraph {
74        &self.default_graph
75    }
76
77    /// Get a named graph
78    pub fn get_graph(&self, name: &str) -> Option<&NamedGraph> {
79        self.graphs.get(name)
80    }
81
82    /// Get all graph names
83    pub fn graph_names(&self) -> Vec<String> {
84        self.graphs.keys().cloned().collect()
85    }
86
87    /// Find triples matching a pattern (SPARQL-like) - optimized with indexing
88    pub fn find_triples(&self, subject: Option<&str>, predicate: Option<&str>, object: Option<&str>) -> Vec<&Triple> {
89        // Use the most selective index to minimize the search space
90        let candidate_indices: SmallVec<[usize; 8]> = match (subject, predicate, object) {
91            // Exact triple lookup - use SPO intersection
92            (Some(s), Some(p), Some(o)) => {
93                self.find_exact_triple_indices(s, p, o)
94            },
95            // Single component lookups - use the most selective index
96            (Some(s), None, None) => {
97                self.subject_index.get(s).cloned().unwrap_or(SmallVec::new())
98            },
99            (None, Some(p), None) => {
100                self.predicate_index.get(p).cloned().unwrap_or(SmallVec::new())
101            },
102            (None, None, Some(o)) => {
103                self.object_index.get(o).cloned().unwrap_or(SmallVec::new())
104            },
105            // Two-component patterns - intersect the two indices
106            (Some(s), Some(p), None) => {
107                self.intersect_indices(
108                    self.subject_index.get(s).map(|v| v.as_slice()).unwrap_or(&[]),
109                    self.predicate_index.get(p).map(|v| v.as_slice()).unwrap_or(&[]),
110                )
111            },
112            (Some(s), None, Some(o)) => {
113                self.intersect_indices(
114                    self.subject_index.get(s).map(|v| v.as_slice()).unwrap_or(&[]),
115                    self.object_index.get(o).map(|v| v.as_slice()).unwrap_or(&[]),
116                )
117            },
118            (None, Some(p), Some(o)) => {
119                self.intersect_indices(
120                    self.predicate_index.get(p).map(|v| v.as_slice()).unwrap_or(&[]),
121                    self.object_index.get(o).map(|v| v.as_slice()).unwrap_or(&[]),
122                )
123            },
124            // No constraints - return all indices
125            (None, None, None) => {
126                (0..self.default_graph.triples.len()).map(|i| i).collect()
127            }
128        };
129
130        // Convert indices to triple references
131        let mut results = Vec::new();
132        for &index in &candidate_indices {
133            if let Some(triple) = self.default_graph.triples.get(index) {
134                results.push(triple);
135            }
136        }
137
138        results
139    }
140
141    /// Find indices of triples that exactly match SPO
142    fn find_exact_triple_indices(&self, subject: &str, predicate: &str, object: &str) -> SmallVec<[usize; 8]> {
143        if let Some(subject_indices) = self.subject_index.get(subject) {
144            if let Some(predicate_indices) = self.predicate_index.get(predicate) {
145                if let Some(object_indices) = self.object_index.get(object) {
146                    // Intersect all three indices
147                    let mut result = SmallVec::new();
148                    for &idx in subject_indices {
149                        if predicate_indices.contains(&idx) && object_indices.contains(&idx) {
150                            result.push(idx);
151                        }
152                    }
153                    return result;
154                }
155            }
156        }
157        SmallVec::new()
158    }
159
160    /// Intersect two sorted index vectors
161    fn intersect_indices(&self, a: &[usize], b: &[usize]) -> SmallVec<[usize; 8]> {
162        let mut result = SmallVec::new();
163        let mut i = 0;
164        let mut j = 0;
165
166        while i < a.len() && j < b.len() {
167            match a[i].cmp(&b[j]) {
168                std::cmp::Ordering::Less => i += 1,
169                std::cmp::Ordering::Greater => j += 1,
170                std::cmp::Ordering::Equal => {
171                    result.push(a[i]);
172                    i += 1;
173                    j += 1;
174                }
175            }
176        }
177
178        result
179    }
180
181    /// Convert store to JSON-LD document
182    pub fn to_jsonld(&self) -> Result<JsonLdDocument> {
183        let mut graph = Vec::new();
184
185        // Add default graph triples
186        for triple in &self.default_graph.triples {
187            let node = serde_json::json!({
188                "@id": triple.subject,
189                triple.predicate.clone(): triple.object
190            });
191            graph.push(node);
192        }
193
194        // Add named graph triples
195        for named_graph in self.graphs.values() {
196            for triple in &named_graph.triples {
197                let node = serde_json::json!({
198                    "@id": triple.subject,
199                    triple.predicate.clone(): triple.object
200                });
201                graph.push(node);
202            }
203        }
204
205        Ok(JsonLdDocument {
206            context: serde_json::json!({
207                "@vocab": "https://w3id.org/security#",
208                "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
209                "rdfs": "http://www.w3.org/2000/01/rdf-schema#"
210            }),
211            graph: Some(graph),
212            data: HashMap::new(),
213        })
214    }
215
216    /// Clear all graphs
217    pub fn clear(&mut self) {
218        self.graphs.clear();
219        self.default_graph.triples.clear();
220        self.subject_index.clear();
221        self.predicate_index.clear();
222        self.object_index.clear();
223    }
224}