Skip to main content

velesdb_core/collection/core/
graph_api.rs

1//! Graph API methods for Collection (EPIC-015 US-001).
2//!
3//! Exposes Knowledge Graph operations on Collection for use by
4//! Tauri plugin, REST API, and other consumers.
5
6use crate::collection::graph::GraphEdge;
7use crate::collection::types::Collection;
8use crate::error::Result;
9
10/// Traversal result for graph operations.
11#[derive(Debug, Clone)]
12pub struct TraversalResult {
13    /// Target node ID reached.
14    pub target_id: u64,
15    /// Depth of traversal.
16    pub depth: u32,
17    /// Path taken (node IDs).
18    pub path: Vec<u64>,
19}
20
21impl Collection {
22    /// Adds an edge to the collection's knowledge graph.
23    ///
24    /// # Arguments
25    ///
26    /// * `edge` - The edge to add (id, source, target, label, properties)
27    ///
28    /// # Errors
29    ///
30    /// Returns `Error::EdgeExists` if an edge with the same ID already exists.
31    ///
32    /// # Example
33    ///
34    /// ```rust,ignore
35    /// use velesdb_core::collection::graph::GraphEdge;
36    ///
37    /// let edge = GraphEdge::new(1, 100, 200, "KNOWS")?;
38    /// collection.add_edge(edge)?;
39    /// ```
40    pub fn add_edge(&self, edge: GraphEdge) -> Result<()> {
41        self.edge_store.write().add_edge(edge)
42    }
43
44    /// Gets all edges from the collection's knowledge graph.
45    ///
46    /// Note: This iterates through all stored edges. For large graphs,
47    /// consider using `get_edges_by_label` or `get_outgoing_edges` for
48    /// more targeted queries.
49    ///
50    /// # Returns
51    ///
52    /// Vector of all edges in the graph (cloned).
53    #[must_use]
54    pub fn get_all_edges(&self) -> Vec<GraphEdge> {
55        let store = self.edge_store.read();
56        store.all_edges().into_iter().cloned().collect()
57    }
58
59    /// Gets edges filtered by label.
60    ///
61    /// # Arguments
62    ///
63    /// * `label` - The edge label (relationship type) to filter by
64    ///
65    /// # Returns
66    ///
67    /// Vector of edges with the specified label (cloned).
68    #[must_use]
69    pub fn get_edges_by_label(&self, label: &str) -> Vec<GraphEdge> {
70        self.edge_store
71            .read()
72            .get_edges_by_label(label)
73            .into_iter()
74            .cloned()
75            .collect()
76    }
77
78    /// Gets outgoing edges from a specific node.
79    ///
80    /// # Arguments
81    ///
82    /// * `node_id` - The source node ID
83    ///
84    /// # Returns
85    ///
86    /// Vector of edges originating from the specified node (cloned).
87    #[must_use]
88    pub fn get_outgoing_edges(&self, node_id: u64) -> Vec<GraphEdge> {
89        self.edge_store
90            .read()
91            .get_outgoing(node_id)
92            .into_iter()
93            .cloned()
94            .collect()
95    }
96
97    /// Gets incoming edges to a specific node.
98    ///
99    /// # Arguments
100    ///
101    /// * `node_id` - The target node ID
102    ///
103    /// # Returns
104    ///
105    /// Vector of edges pointing to the specified node (cloned).
106    #[must_use]
107    pub fn get_incoming_edges(&self, node_id: u64) -> Vec<GraphEdge> {
108        self.edge_store
109            .read()
110            .get_incoming(node_id)
111            .into_iter()
112            .cloned()
113            .collect()
114    }
115
116    /// Traverses the graph using BFS from a source node.
117    ///
118    /// # Arguments
119    ///
120    /// * `source` - Starting node ID
121    /// * `max_depth` - Maximum traversal depth
122    /// * `rel_types` - Optional filter by relationship types
123    /// * `limit` - Maximum number of results
124    ///
125    /// # Returns
126    ///
127    /// Vector of traversal results with target nodes and paths.
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if traversal fails.
132    pub fn traverse_bfs(
133        &self,
134        source: u64,
135        max_depth: u32,
136        rel_types: Option<&[&str]>,
137        limit: usize,
138    ) -> Result<Vec<TraversalResult>> {
139        use std::collections::{HashSet, VecDeque};
140
141        let store = self.edge_store.read();
142        let mut visited = HashSet::new();
143        let mut queue = VecDeque::new();
144        let mut results = Vec::new();
145
146        visited.insert(source);
147        queue.push_back((source, 0u32, vec![source]));
148
149        while let Some((node, depth, path)) = queue.pop_front() {
150            if results.len() >= limit {
151                break;
152            }
153
154            if depth >= max_depth {
155                continue;
156            }
157
158            for edge in store.get_outgoing(node) {
159                // Filter by relationship type if specified
160                if let Some(types) = rel_types {
161                    if !types.contains(&edge.label()) {
162                        continue;
163                    }
164                }
165
166                let target = edge.target();
167                if !visited.contains(&target) {
168                    visited.insert(target);
169                    let mut new_path = path.clone();
170                    new_path.push(target);
171
172                    results.push(TraversalResult {
173                        target_id: target,
174                        depth: depth + 1,
175                        path: new_path.clone(),
176                    });
177
178                    if results.len() < limit {
179                        queue.push_back((target, depth + 1, new_path));
180                    }
181                }
182            }
183        }
184
185        Ok(results)
186    }
187
188    /// Traverses the graph using DFS from a source node.
189    ///
190    /// # Arguments
191    ///
192    /// * `source` - Starting node ID
193    /// * `max_depth` - Maximum traversal depth
194    /// * `rel_types` - Optional filter by relationship types
195    /// * `limit` - Maximum number of results
196    ///
197    /// # Returns
198    ///
199    /// Vector of traversal results with target nodes and paths.
200    ///
201    /// # Errors
202    ///
203    /// Returns an error if traversal fails.
204    pub fn traverse_dfs(
205        &self,
206        source: u64,
207        max_depth: u32,
208        rel_types: Option<&[&str]>,
209        limit: usize,
210    ) -> Result<Vec<TraversalResult>> {
211        use std::collections::HashSet;
212
213        let store = self.edge_store.read();
214        let mut visited = HashSet::new();
215        let mut stack = Vec::new();
216        let mut results = Vec::new();
217
218        visited.insert(source);
219        stack.push((source, 0u32, vec![source]));
220
221        while let Some((node, depth, path)) = stack.pop() {
222            if results.len() >= limit {
223                break;
224            }
225
226            if depth >= max_depth {
227                continue;
228            }
229
230            for edge in store.get_outgoing(node) {
231                // Filter by relationship type if specified
232                if let Some(types) = rel_types {
233                    if !types.contains(&edge.label()) {
234                        continue;
235                    }
236                }
237
238                let target = edge.target();
239                if !visited.contains(&target) {
240                    visited.insert(target);
241                    let mut new_path = path.clone();
242                    new_path.push(target);
243
244                    results.push(TraversalResult {
245                        target_id: target,
246                        depth: depth + 1,
247                        path: new_path.clone(),
248                    });
249
250                    if results.len() < limit {
251                        stack.push((target, depth + 1, new_path));
252                    }
253                }
254            }
255        }
256
257        Ok(results)
258    }
259
260    /// Gets the in-degree and out-degree of a node.
261    ///
262    /// # Arguments
263    ///
264    /// * `node_id` - The node ID
265    ///
266    /// # Returns
267    ///
268    /// Tuple of (`in_degree`, `out_degree`).
269    #[must_use]
270    pub fn get_node_degree(&self, node_id: u64) -> (usize, usize) {
271        let store = self.edge_store.read();
272        let in_degree = store.get_incoming(node_id).len();
273        let out_degree = store.get_outgoing(node_id).len();
274        (in_degree, out_degree)
275    }
276
277    /// Removes an edge from the graph by ID.
278    ///
279    /// # Arguments
280    ///
281    /// * `edge_id` - The edge ID to remove
282    ///
283    /// # Returns
284    ///
285    /// `true` if the edge existed and was removed, `false` if it didn't exist.
286    #[must_use]
287    pub fn remove_edge(&self, edge_id: u64) -> bool {
288        let mut store = self.edge_store.write();
289        if store.contains_edge(edge_id) {
290            store.remove_edge(edge_id);
291            true
292        } else {
293            false
294        }
295    }
296
297    /// Returns the total number of edges in the graph.
298    #[must_use]
299    pub fn edge_count(&self) -> usize {
300        let store = self.edge_store.read();
301        store.len()
302    }
303}