1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::sync::atomic::{AtomicU64, Ordering};
4
5use parking_lot::{Mutex, RwLock};
6
7use crate::error::{Result, VantaError};
8use crate::node::{FieldValue, UnifiedNode, VectorRepresentations};
9use crate::wal::{WalReader, WalRecord, WalWriter};
10
11#[derive(Debug, Clone)]
15pub enum SourceType {
16 FullScan,
17 BitsetFilter,
18 VectorSearch,
19 GraphTraversal,
20 Hybrid,
21}
22
23#[derive(Debug, Clone)]
25pub struct QueryResult {
26 pub nodes: Vec<UnifiedNode>,
27 pub is_partial: bool,
29 pub exhaustivity: f32,
31 pub source_type: SourceType,
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct EngineStats {
38 pub node_count: u64,
39 pub edge_count: u64,
40 pub vector_count: u64,
41 pub total_dimensions: u64,
42 pub memory_estimate_bytes: u64,
43}
44
45pub struct InMemoryEngine {
52 nodes: RwLock<HashMap<u64, UnifiedNode>>,
53 wal: Mutex<Option<WalWriter>>,
54 next_id: AtomicU64,
55 #[allow(dead_code)]
56 wal_path: Option<PathBuf>,
57}
58
59impl InMemoryEngine {
60 pub fn new() -> Self {
62 Self {
63 nodes: RwLock::new(HashMap::with_capacity(1024)),
64 wal: Mutex::new(None),
65 next_id: AtomicU64::new(1),
66 wal_path: None,
67 }
68 }
69
70 pub fn with_wal(wal_path: impl AsRef<Path>) -> Result<Self> {
72 let path = wal_path.as_ref().to_path_buf();
73 let mut nodes_map = HashMap::with_capacity(1024);
74 let mut max_id: u64 = 0;
75
76 if path.exists() {
78 let mut reader = WalReader::open(&path)?;
79 reader.replay_all(|record| {
80 match record {
81 WalRecord::Insert(node) => {
82 max_id = max_id.max(node.id);
83 nodes_map.insert(node.id, node);
84 }
85 WalRecord::Update { id, node } => {
86 max_id = max_id.max(id);
87 nodes_map.insert(id, node);
88 }
89 WalRecord::Delete { id } => {
90 nodes_map.remove(&id);
91 }
92 WalRecord::Checkpoint { .. } => {}
93 }
94 Ok(())
95 })?;
96 }
97
98 let writer = WalWriter::open(&path, crate::config::SyncMode::Periodic)?;
99
100 Ok(Self {
101 nodes: RwLock::new(nodes_map),
102 wal: Mutex::new(Some(writer)),
103 next_id: AtomicU64::new(max_id + 1),
104 wal_path: Some(path),
105 })
106 }
107
108 pub fn next_id(&self) -> u64 {
110 self.next_id.fetch_add(1, Ordering::SeqCst)
111 }
112
113 pub fn insert(&self, mut node: UnifiedNode) -> Result<u64> {
115 if node.id == 0 {
116 node.id = self.next_id();
117 }
118 let id = node.id;
119
120 if let Some(ref mut wal) = *self.wal.lock() {
122 wal.append(&WalRecord::Insert(node.clone()))?;
123 }
124
125 let mut nodes = self.nodes.write();
126 if nodes.contains_key(&id) {
127 return Err(VantaError::DuplicateNode(id));
128 }
129 nodes.insert(id, node);
130 Ok(id)
131 }
132
133 pub fn get(&self, id: u64) -> Option<UnifiedNode> {
135 self.nodes.read().get(&id).cloned()
136 }
137
138 pub fn contains(&self, id: u64) -> bool {
140 self.nodes.read().contains_key(&id)
141 }
142
143 pub fn update(&self, id: u64, node: UnifiedNode) -> Result<()> {
145 if let Some(ref mut wal) = *self.wal.lock() {
146 wal.append(&WalRecord::Update {
147 id,
148 node: node.clone(),
149 })?;
150 }
151 let mut nodes = self.nodes.write();
152 if !nodes.contains_key(&id) {
153 return Err(VantaError::NodeNotFound(id));
154 }
155 nodes.insert(id, node);
156 Ok(())
157 }
158
159 pub fn delete(&self, id: u64) -> Result<()> {
161 if let Some(ref mut wal) = *self.wal.lock() {
162 wal.append(&WalRecord::Delete { id })?;
163 }
164 let mut nodes = self.nodes.write();
165 if nodes.remove(&id).is_none() {
166 return Err(VantaError::NodeNotFound(id));
167 }
168 Ok(())
169 }
170
171 pub fn scan_bitset(&self, mask: u128) -> Vec<u64> {
173 self.nodes
174 .read()
175 .values()
176 .filter(|n| n.is_alive() && n.matches_mask(mask))
177 .map(|n| n.id)
178 .collect()
179 }
180
181 pub fn vector_search(
184 &self,
185 query: &[f32],
186 top_k: usize,
187 min_score: f32,
188 bitset_filter: Option<u128>,
189 ) -> QueryResult {
190 let query_vec = VectorRepresentations::Full(query.to_vec());
191 let nodes = self.nodes.read();
192
193 let mut scored: Vec<(u64, f32)> = nodes
194 .values()
195 .filter(|n| {
196 n.is_alive()
197 && !n.vector.is_none()
198 && bitset_filter.is_none_or(|m| n.matches_mask(m))
199 })
200 .filter_map(|n| {
201 n.vector
202 .cosine_similarity(&query_vec)
203 .filter(|&s| s >= min_score)
204 .map(|s| (n.id, s))
205 })
206 .collect();
207
208 scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
209 scored.truncate(top_k);
210
211 let result_nodes: Vec<UnifiedNode> = scored
212 .iter()
213 .filter_map(|(id, _)| nodes.get(id).cloned())
214 .collect();
215
216 QueryResult {
217 nodes: result_nodes,
218 is_partial: false,
219 exhaustivity: 1.0, source_type: if bitset_filter.is_some() {
221 SourceType::Hybrid
222 } else {
223 SourceType::VectorSearch
224 },
225 }
226 }
227
228 pub fn traverse(
231 &self,
232 start: u64,
233 label: &str,
234 min_depth: u32,
235 max_depth: u32,
236 ) -> Result<Vec<(u64, u32)>> {
237 let nodes = self.nodes.read();
238 if !nodes.contains_key(&start) {
239 return Err(VantaError::NodeNotFound(start));
240 }
241
242 let mut visited = HashMap::new();
243 let mut queue = std::collections::VecDeque::new();
244 queue.push_back((start, 0u32));
245 visited.insert(start, 0u32);
246
247 let mut results = Vec::new();
248
249 while let Some((current_id, depth)) = queue.pop_front() {
250 if depth >= max_depth {
251 continue;
252 }
253 if let Some(node) = nodes.get(¤t_id) {
254 for edge in &node.edges {
255 if edge.label == label {
256 if let std::collections::hash_map::Entry::Vacant(e) =
257 visited.entry(edge.target)
258 {
259 let next_depth = depth + 1;
260 e.insert(next_depth);
261 if next_depth >= min_depth {
262 results.push((edge.target, next_depth));
263 }
264 queue.push_back((edge.target, next_depth));
265 }
266 }
267 }
268 }
269 }
270 Ok(results)
271 }
272
273 pub fn filter_field(&self, field: &str, value: &FieldValue) -> Vec<u64> {
275 self.nodes
276 .read()
277 .values()
278 .filter(|n| n.is_alive() && n.get_field(field) == Some(value))
279 .map(|n| n.id)
280 .collect()
281 }
282
283 pub fn hybrid_search(
286 &self,
287 query_vector: &[f32],
288 top_k: usize,
289 min_score: f32,
290 bitset_mask: Option<u128>,
291 field_filters: &[(String, FieldValue)],
292 ) -> QueryResult {
293 let query_vec = VectorRepresentations::Full(query_vector.to_vec());
294 let nodes = self.nodes.read();
295
296 let mut scored: Vec<(u64, f32)> = nodes
297 .values()
298 .filter(|n| {
299 if !n.is_alive() || n.vector.is_none() {
300 return false;
301 }
302 if let Some(mask) = bitset_mask {
304 if !n.matches_mask(mask) {
305 return false;
306 }
307 }
308 for (field, value) in field_filters {
310 if n.get_field(field) != Some(value) {
311 return false;
312 }
313 }
314 true
315 })
316 .filter_map(|n| {
317 n.vector
318 .cosine_similarity(&query_vec)
319 .filter(|&s| s >= min_score)
320 .map(|s| (n.id, s))
321 })
322 .collect();
323
324 scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
325 scored.truncate(top_k);
326
327 let result_nodes = scored
328 .iter()
329 .filter_map(|(id, _)| nodes.get(id).cloned())
330 .collect();
331
332 QueryResult {
333 nodes: result_nodes,
334 is_partial: false,
335 exhaustivity: 1.0,
336 source_type: SourceType::Hybrid,
337 }
338 }
339
340 pub fn flush_wal(&self) -> Result<()> {
342 if let Some(ref mut wal) = *self.wal.lock() {
343 wal.sync()?;
344 }
345 Ok(())
346 }
347
348 pub fn node_count(&self) -> usize {
350 self.nodes.read().values().filter(|n| n.is_alive()).count()
351 }
352
353 pub fn stats(&self) -> EngineStats {
355 let nodes = self.nodes.read();
356 let mut stats = EngineStats::default();
357 for node in nodes.values() {
358 if !node.is_alive() {
359 continue;
360 }
361 stats.node_count += 1;
362 stats.edge_count += node.edges.len() as u64;
363 if !node.vector.is_none() {
364 stats.vector_count += 1;
365 stats.total_dimensions += node.vector.dimensions() as u64;
366 }
367 stats.memory_estimate_bytes += node.memory_size() as u64;
368 }
369 stats
370 }
371}
372
373impl Default for InMemoryEngine {
374 fn default() -> Self {
375 Self::new()
376 }
377}