Skip to main content

nodedb_types/
result.rs

1//! Query result types returned by the `NodeDb` trait methods.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::id::{EdgeId, NodeId};
8use crate::value::Value;
9
10/// Result of a k-NN vector search.
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct SearchResult {
13    /// Document/vector ID.
14    pub id: String,
15    /// Corresponding graph node ID (if the vector is linked to a node).
16    pub node_id: Option<NodeId>,
17    /// Distance from the query vector under the configured metric.
18    pub distance: f32,
19    /// Optional metadata fields attached to this vector.
20    pub metadata: HashMap<String, Value>,
21}
22
23/// Result of a graph traversal (BFS/DFS).
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct SubGraph {
26    /// Nodes discovered during traversal, keyed by node ID.
27    pub nodes: Vec<SubGraphNode>,
28    /// Edges traversed.
29    pub edges: Vec<SubGraphEdge>,
30}
31
32/// A node in a traversal result.
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct SubGraphNode {
35    /// Node identifier.
36    pub id: NodeId,
37    /// Depth at which this node was discovered (0 = start node).
38    pub depth: u8,
39    /// Optional properties attached to this node.
40    pub properties: HashMap<String, Value>,
41}
42
43/// An edge in a traversal result.
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct SubGraphEdge {
46    /// Edge identifier.
47    pub id: EdgeId,
48    /// Source node.
49    pub from: NodeId,
50    /// Target node.
51    pub to: NodeId,
52    /// Edge label/type (e.g., "KNOWS", "RELATES_TO").
53    pub label: String,
54    /// Optional properties attached to this edge.
55    pub properties: HashMap<String, Value>,
56}
57
58/// Result of a SQL query or multi-modal query.
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60pub struct QueryResult {
61    /// Column names.
62    pub columns: Vec<String>,
63    /// Rows of values. Each row has one value per column.
64    pub rows: Vec<Vec<Value>>,
65    /// Number of rows affected (for INSERT/UPDATE/DELETE).
66    pub rows_affected: u64,
67}
68
69impl QueryResult {
70    /// Empty result set with no columns.
71    pub fn empty() -> Self {
72        Self {
73            columns: Vec::new(),
74            rows: Vec::new(),
75            rows_affected: 0,
76        }
77    }
78
79    /// Number of rows in the result set.
80    pub fn row_count(&self) -> usize {
81        self.rows.len()
82    }
83
84    /// Get a single row as a `HashMap<column_name, value>`.
85    pub fn row_as_map(&self, index: usize) -> Option<HashMap<&str, &Value>> {
86        let row = self.rows.get(index)?;
87        Some(
88            self.columns
89                .iter()
90                .zip(row.iter())
91                .map(|(col, val)| (col.as_str(), val))
92                .collect(),
93        )
94    }
95}
96
97impl SubGraph {
98    /// Empty subgraph.
99    pub fn empty() -> Self {
100        Self {
101            nodes: Vec::new(),
102            edges: Vec::new(),
103        }
104    }
105
106    /// Number of nodes in the subgraph.
107    pub fn node_count(&self) -> usize {
108        self.nodes.len()
109    }
110
111    /// Number of edges in the subgraph.
112    pub fn edge_count(&self) -> usize {
113        self.edges.len()
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn search_result() {
123        let r = SearchResult {
124            id: "vec-1".into(),
125            node_id: Some(NodeId::new("node-1")),
126            distance: 0.123,
127            metadata: HashMap::new(),
128        };
129        assert_eq!(r.id, "vec-1");
130        assert!(r.distance < 1.0);
131    }
132
133    #[test]
134    fn subgraph_construction() {
135        let mut sg = SubGraph::empty();
136        sg.nodes.push(SubGraphNode {
137            id: NodeId::new("a"),
138            depth: 0,
139            properties: HashMap::new(),
140        });
141        sg.nodes.push(SubGraphNode {
142            id: NodeId::new("b"),
143            depth: 1,
144            properties: HashMap::new(),
145        });
146        sg.edges.push(SubGraphEdge {
147            id: EdgeId::from_components("a", "b", "KNOWS"),
148            from: NodeId::new("a"),
149            to: NodeId::new("b"),
150            label: "KNOWS".into(),
151            properties: HashMap::new(),
152        });
153        assert_eq!(sg.node_count(), 2);
154        assert_eq!(sg.edge_count(), 1);
155    }
156
157    #[test]
158    fn query_result_row_as_map() {
159        let qr = QueryResult {
160            columns: vec!["name".into(), "age".into()],
161            rows: vec![vec![Value::String("Alice".into()), Value::Integer(30)]],
162            rows_affected: 0,
163        };
164        let row = qr.row_as_map(0).unwrap();
165        assert_eq!(row["name"].as_str(), Some("Alice"));
166        assert_eq!(row["age"].as_i64(), Some(30));
167    }
168
169    #[test]
170    fn query_result_empty() {
171        let qr = QueryResult::empty();
172        assert_eq!(qr.row_count(), 0);
173        assert!(qr.row_as_map(0).is_none());
174    }
175}