1use crate::error::Result;
2use crate::graph::ArrowGraph;
3use arrow::array::{Array, StringArray, Float64Array, RecordBatch};
4use arrow::datatypes::{Schema, Field, DataType};
5use std::collections::{HashMap, HashSet};
6use std::sync::Arc;
7
8#[derive(Debug, Clone)]
11pub struct IncrementalGraphProcessor {
12 graph: ArrowGraph,
13 vertex_cache: HashMap<String, usize>, edge_cache: HashMap<(String, String), f64>, pending_vertex_additions: Vec<String>,
16 pending_vertex_removals: HashSet<String>,
17 pending_edge_additions: Vec<(String, String, f64)>,
18 pending_edge_removals: HashSet<(String, String)>,
19 batch_size: usize, }
21
22#[derive(Debug, Clone, PartialEq)]
24pub enum UpdateOperation {
25 AddVertex(String),
26 RemoveVertex(String),
27 AddEdge(String, String, f64), RemoveEdge(String, String), }
30
31#[derive(Debug, Clone)]
33pub struct UpdateResult {
34 pub vertices_added: usize,
35 pub vertices_removed: usize,
36 pub edges_added: usize,
37 pub edges_removed: usize,
38 pub affected_components: Vec<String>, pub recomputation_needed: bool, }
41
42impl IncrementalGraphProcessor {
43 pub fn new(graph: ArrowGraph) -> Result<Self> {
45 let mut vertex_cache = HashMap::new();
46 let mut edge_cache = HashMap::new();
47
48 let nodes_batch = &graph.nodes;
50 if nodes_batch.num_rows() > 0 {
51 let node_ids = nodes_batch
52 .column(0)
53 .as_any()
54 .downcast_ref::<StringArray>()
55 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
56
57 for (idx, node_id) in node_ids.iter().enumerate() {
58 if let Some(id) = node_id {
59 vertex_cache.insert(id.to_string(), idx);
60 }
61 }
62 }
63
64 let edges_batch = &graph.edges;
65 if edges_batch.num_rows() > 0 {
66 let source_ids = edges_batch
67 .column(0)
68 .as_any()
69 .downcast_ref::<StringArray>()
70 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
71 let target_ids = edges_batch
72 .column(1)
73 .as_any()
74 .downcast_ref::<StringArray>()
75 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
76 let weights = edges_batch
77 .column(2)
78 .as_any()
79 .downcast_ref::<Float64Array>()
80 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected float64 array for weights"))?;
81
82 for i in 0..source_ids.len() {
83 let source = source_ids.value(i);
84 let target = target_ids.value(i);
85 let weight = weights.value(i);
86 edge_cache.insert((source.to_string(), target.to_string()), weight);
87 }
88 }
89
90 Ok(Self {
91 graph,
92 vertex_cache,
93 edge_cache,
94 pending_vertex_additions: Vec::new(),
95 pending_vertex_removals: HashSet::new(),
96 pending_edge_additions: Vec::new(),
97 pending_edge_removals: HashSet::new(),
98 batch_size: 1000, })
100 }
101
102 pub fn set_batch_size(&mut self, size: usize) {
104 self.batch_size = size;
105 }
106
107 pub fn add_vertex(&mut self, vertex_id: String) -> Result<()> {
109 if self.vertex_cache.contains_key(&vertex_id) ||
111 self.pending_vertex_additions.contains(&vertex_id) {
112 return Ok(()); }
114
115 self.pending_vertex_removals.remove(&vertex_id);
117
118 self.pending_vertex_additions.push(vertex_id);
120
121 if self.pending_vertex_additions.len() >= self.batch_size {
123 self.flush_vertex_operations()?;
124 }
125
126 Ok(())
127 }
128
129 pub fn remove_vertex(&mut self, vertex_id: String) -> Result<()> {
131 if !self.vertex_cache.contains_key(&vertex_id) &&
133 !self.pending_vertex_additions.contains(&vertex_id) {
134 return Ok(()); }
136
137 self.pending_vertex_additions.retain(|v| v != &vertex_id);
139
140 self.pending_vertex_removals.insert(vertex_id.clone());
142
143 let edges_to_remove: Vec<(String, String)> = self.edge_cache
145 .keys()
146 .filter(|(source, target)| source == &vertex_id || target == &vertex_id)
147 .cloned()
148 .collect();
149
150 for (source, target) in edges_to_remove {
151 self.pending_edge_removals.insert((source, target));
152 }
153
154 if self.pending_vertex_removals.len() >= self.batch_size {
156 self.flush_all_operations()?;
157 }
158
159 Ok(())
160 }
161
162 pub fn add_edge(&mut self, source: String, target: String, weight: f64) -> Result<()> {
164 let edge_key = (source.clone(), target.clone());
165
166 if self.edge_cache.contains_key(&edge_key) {
168 if let Some(existing_weight) = self.edge_cache.get(&edge_key) {
170 if (existing_weight - weight).abs() > f64::EPSILON {
171 self.edge_cache.insert(edge_key.clone(), weight);
172 }
173 }
174 return Ok(());
175 }
176
177 self.pending_edge_removals.remove(&edge_key);
179
180 self.add_vertex(source.clone())?;
182 self.add_vertex(target.clone())?;
183
184 self.pending_edge_additions.push((source, target, weight));
186
187 if self.pending_edge_additions.len() >= self.batch_size {
189 self.flush_edge_operations()?;
190 }
191
192 Ok(())
193 }
194
195 pub fn remove_edge(&mut self, source: String, target: String) -> Result<()> {
197 let edge_key = (source.clone(), target.clone());
198
199 if !self.edge_cache.contains_key(&edge_key) {
201 return Ok(()); }
203
204 self.pending_edge_additions.retain(|(s, t, _)| (s, t) != (&source, &target));
206
207 self.pending_edge_removals.insert(edge_key);
209
210 if self.pending_edge_removals.len() >= self.batch_size {
212 self.flush_edge_operations()?;
213 }
214
215 Ok(())
216 }
217
218 pub fn apply_updates(&mut self, operations: Vec<UpdateOperation>) -> Result<UpdateResult> {
220 for operation in operations {
221 match operation {
222 UpdateOperation::AddVertex(vertex_id) => self.add_vertex(vertex_id)?,
223 UpdateOperation::RemoveVertex(vertex_id) => self.remove_vertex(vertex_id)?,
224 UpdateOperation::AddEdge(source, target, weight) => self.add_edge(source, target, weight)?,
225 UpdateOperation::RemoveEdge(source, target) => self.remove_edge(source, target)?,
226 }
227 }
228
229 self.flush_all_operations()
231 }
232
233 pub fn flush_all_operations(&mut self) -> Result<UpdateResult> {
235 let mut result = UpdateResult {
236 vertices_added: 0,
237 vertices_removed: 0,
238 edges_added: 0,
239 edges_removed: 0,
240 affected_components: Vec::new(),
241 recomputation_needed: false,
242 };
243
244 let vertex_result = self.flush_vertex_operations()?;
246 result.vertices_added += vertex_result.vertices_added;
247 result.vertices_removed += vertex_result.vertices_removed;
248
249 let edge_result = self.flush_edge_operations()?;
251 result.edges_added += edge_result.edges_added;
252 result.edges_removed += edge_result.edges_removed;
253
254 result.recomputation_needed = result.vertices_added > 0 ||
256 result.vertices_removed > 0 ||
257 result.edges_added > 0 ||
258 result.edges_removed > 0;
259
260 Ok(result)
261 }
262
263 fn flush_vertex_operations(&mut self) -> Result<UpdateResult> {
265 let mut result = UpdateResult {
266 vertices_added: 0,
267 vertices_removed: 0,
268 edges_added: 0,
269 edges_removed: 0,
270 affected_components: Vec::new(),
271 recomputation_needed: false,
272 };
273
274 for vertex_id in &self.pending_vertex_additions {
276 if !self.vertex_cache.contains_key(vertex_id) {
277 let new_index = self.vertex_cache.len();
278 self.vertex_cache.insert(vertex_id.clone(), new_index);
279 result.vertices_added += 1;
280 }
281 }
282
283 for vertex_id in &self.pending_vertex_removals {
285 if self.vertex_cache.remove(vertex_id).is_some() {
286 result.vertices_removed += 1;
287 }
288 }
289
290 if result.vertices_added > 0 || result.vertices_removed > 0 {
292 self.rebuild_graph()?;
293 }
294
295 self.pending_vertex_additions.clear();
297 self.pending_vertex_removals.clear();
298
299 Ok(result)
300 }
301
302 fn flush_edge_operations(&mut self) -> Result<UpdateResult> {
304 let mut result = UpdateResult {
305 vertices_added: 0,
306 vertices_removed: 0,
307 edges_added: 0,
308 edges_removed: 0,
309 affected_components: Vec::new(),
310 recomputation_needed: false,
311 };
312
313 for (source, target, weight) in &self.pending_edge_additions {
315 let edge_key = (source.clone(), target.clone());
316 if !self.edge_cache.contains_key(&edge_key) {
317 self.edge_cache.insert(edge_key, *weight);
318 result.edges_added += 1;
319 }
320 }
321
322 for edge_key in &self.pending_edge_removals {
324 if self.edge_cache.remove(edge_key).is_some() {
325 result.edges_removed += 1;
326 }
327 }
328
329 if result.edges_added > 0 || result.edges_removed > 0 {
331 self.rebuild_graph()?;
332 }
333
334 self.pending_edge_additions.clear();
336 self.pending_edge_removals.clear();
337
338 Ok(result)
339 }
340
341 fn rebuild_graph(&mut self) -> Result<()> {
343 let node_ids: Vec<String> = self.vertex_cache.keys().cloned().collect();
345 let nodes_schema = Arc::new(Schema::new(vec![
346 Field::new("id", DataType::Utf8, false),
347 ]));
348
349 let nodes_batch = if node_ids.is_empty() {
350 None
351 } else {
352 let node_id_array = StringArray::from(node_ids);
353 Some(RecordBatch::try_new(
354 nodes_schema.clone(),
355 vec![Arc::new(node_id_array)],
356 )?)
357 };
358
359 let edges_schema = Arc::new(Schema::new(vec![
361 Field::new("source", DataType::Utf8, false),
362 Field::new("target", DataType::Utf8, false),
363 Field::new("weight", DataType::Float64, false),
364 ]));
365
366 let edges_batch = if self.edge_cache.is_empty() {
367 None
368 } else {
369 let mut sources = Vec::new();
370 let mut targets = Vec::new();
371 let mut weights = Vec::new();
372
373 for ((source, target), weight) in &self.edge_cache {
374 sources.push(source.clone());
375 targets.push(target.clone());
376 weights.push(*weight);
377 }
378
379 let source_array = StringArray::from(sources);
380 let target_array = StringArray::from(targets);
381 let weight_array = Float64Array::from(weights);
382
383 Some(RecordBatch::try_new(
384 edges_schema.clone(),
385 vec![Arc::new(source_array), Arc::new(target_array), Arc::new(weight_array)],
386 )?)
387 };
388
389 match (nodes_batch, edges_batch) {
391 (Some(nodes), Some(edges)) => {
392 self.graph = ArrowGraph::new(nodes, edges)?;
393 }
394 (Some(nodes), None) => {
395 let edges_schema = Arc::new(Schema::new(vec![
397 Field::new("source", DataType::Utf8, false),
398 Field::new("target", DataType::Utf8, false),
399 Field::new("weight", DataType::Float64, false),
400 ]));
401 let empty_edges = RecordBatch::new_empty(edges_schema);
402 self.graph = ArrowGraph::new(nodes, empty_edges)?;
403 }
404 (None, Some(edges)) => {
405 self.graph = ArrowGraph::from_edges(edges)?;
406 }
407 (None, None) => {
408 self.graph = ArrowGraph::empty()?;
409 }
410 }
411
412 Ok(())
413 }
414
415 pub fn graph(&self) -> &ArrowGraph {
417 &self.graph
418 }
419
420 pub fn pending_operations_count(&self) -> usize {
422 self.pending_vertex_additions.len() +
423 self.pending_vertex_removals.len() +
424 self.pending_edge_additions.len() +
425 self.pending_edge_removals.len()
426 }
427
428 pub fn has_pending_operations(&self) -> bool {
430 self.pending_operations_count() > 0
431 }
432
433 pub fn statistics(&self) -> IncrementalStats {
435 IncrementalStats {
436 vertex_count: self.vertex_cache.len(),
437 edge_count: self.edge_cache.len(),
438 pending_vertex_additions: self.pending_vertex_additions.len(),
439 pending_vertex_removals: self.pending_vertex_removals.len(),
440 pending_edge_additions: self.pending_edge_additions.len(),
441 pending_edge_removals: self.pending_edge_removals.len(),
442 batch_size: self.batch_size,
443 }
444 }
445}
446
447#[derive(Debug, Clone)]
449pub struct IncrementalStats {
450 pub vertex_count: usize,
451 pub edge_count: usize,
452 pub pending_vertex_additions: usize,
453 pub pending_vertex_removals: usize,
454 pub pending_edge_additions: usize,
455 pub pending_edge_removals: usize,
456 pub batch_size: usize,
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462 use arrow::datatypes::{Schema, Field, DataType};
463 use std::sync::Arc;
464
465 fn create_test_graph() -> Result<ArrowGraph> {
466 let nodes_schema = Arc::new(Schema::new(vec![
468 Field::new("id", DataType::Utf8, false),
469 ]));
470 let node_ids = StringArray::from(vec!["A", "B", "C"]);
471 let nodes_batch = RecordBatch::try_new(
472 nodes_schema,
473 vec![Arc::new(node_ids)],
474 )?;
475
476 let edges_schema = Arc::new(Schema::new(vec![
478 Field::new("source", DataType::Utf8, false),
479 Field::new("target", DataType::Utf8, false),
480 Field::new("weight", DataType::Float64, false),
481 ]));
482 let sources = StringArray::from(vec!["A", "B"]);
483 let targets = StringArray::from(vec!["B", "C"]);
484 let weights = Float64Array::from(vec![1.0, 2.0]);
485 let edges_batch = RecordBatch::try_new(
486 edges_schema,
487 vec![Arc::new(sources), Arc::new(targets), Arc::new(weights)],
488 )?;
489
490 ArrowGraph::new(nodes_batch, edges_batch)
491 }
492
493 #[test]
494 fn test_incremental_processor_creation() {
495 let graph = create_test_graph().unwrap();
496 let processor = IncrementalGraphProcessor::new(graph).unwrap();
497
498 let stats = processor.statistics();
499 assert_eq!(stats.vertex_count, 3);
500 assert_eq!(stats.edge_count, 2);
501 assert_eq!(stats.pending_vertex_additions, 0);
502 assert_eq!(stats.pending_edge_additions, 0);
503 }
504
505 #[test]
506 fn test_add_vertex() {
507 let graph = create_test_graph().unwrap();
508 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
509 processor.set_batch_size(1); processor.add_vertex("D".to_string()).unwrap();
512
513 let stats = processor.statistics();
514 assert_eq!(stats.vertex_count, 4);
515 assert!(processor.vertex_cache.contains_key("D"));
516 }
517
518 #[test]
519 fn test_remove_vertex() {
520 let graph = create_test_graph().unwrap();
521 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
522 processor.set_batch_size(1); processor.remove_vertex("C".to_string()).unwrap();
525
526 let stats = processor.statistics();
527 assert_eq!(stats.vertex_count, 2);
528 assert!(!processor.vertex_cache.contains_key("C"));
529 assert!(!processor.edge_cache.contains_key(&("B".to_string(), "C".to_string())));
531 }
532
533 #[test]
534 fn test_add_edge() {
535 let graph = create_test_graph().unwrap();
536 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
537 processor.set_batch_size(1); processor.add_edge("A".to_string(), "C".to_string(), 3.0).unwrap();
540
541 let stats = processor.statistics();
542 assert_eq!(stats.edge_count, 3);
543 assert_eq!(
544 processor.edge_cache.get(&("A".to_string(), "C".to_string())),
545 Some(&3.0)
546 );
547 }
548
549 #[test]
550 fn test_remove_edge() {
551 let graph = create_test_graph().unwrap();
552 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
553 processor.set_batch_size(1); processor.remove_edge("A".to_string(), "B".to_string()).unwrap();
556
557 let stats = processor.statistics();
558 assert_eq!(stats.edge_count, 1);
559 assert!(!processor.edge_cache.contains_key(&("A".to_string(), "B".to_string())));
560 }
561
562 #[test]
563 fn test_batch_operations() {
564 let graph = create_test_graph().unwrap();
565 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
566 processor.set_batch_size(10); processor.add_vertex("D".to_string()).unwrap();
570 processor.add_vertex("E".to_string()).unwrap();
571 processor.add_edge("D".to_string(), "E".to_string(), 4.0).unwrap();
572
573 assert!(processor.has_pending_operations());
575 assert_eq!(processor.pending_operations_count(), 3);
576
577 let result = processor.flush_all_operations().unwrap();
579
580 assert_eq!(result.vertices_added, 2);
581 assert_eq!(result.edges_added, 1);
582 assert!(!processor.has_pending_operations());
583
584 let stats = processor.statistics();
585 assert_eq!(stats.vertex_count, 5);
586 assert_eq!(stats.edge_count, 3);
587 }
588
589 #[test]
590 fn test_apply_updates() {
591 let graph = create_test_graph().unwrap();
592 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
593
594 let operations = vec![
595 UpdateOperation::AddVertex("D".to_string()),
596 UpdateOperation::AddVertex("E".to_string()),
597 UpdateOperation::AddEdge("D".to_string(), "E".to_string(), 5.0),
598 UpdateOperation::RemoveEdge("A".to_string(), "B".to_string()),
599 UpdateOperation::RemoveVertex("C".to_string()),
600 ];
601
602 let result = processor.apply_updates(operations).unwrap();
603
604 assert_eq!(result.vertices_added, 2);
605 assert_eq!(result.vertices_removed, 1);
606 assert_eq!(result.edges_added, 1);
607 assert_eq!(result.edges_removed, 2); assert!(result.recomputation_needed);
609
610 let stats = processor.statistics();
611 assert_eq!(stats.vertex_count, 4); assert_eq!(stats.edge_count, 1); }
614
615 #[test]
616 fn test_duplicate_operations() {
617 let graph = create_test_graph().unwrap();
618 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
619 processor.set_batch_size(1); processor.add_vertex("D".to_string()).unwrap();
623 processor.add_vertex("D".to_string()).unwrap();
624
625 let stats = processor.statistics();
626 assert_eq!(stats.vertex_count, 4); processor.remove_vertex("Z".to_string()).unwrap();
630
631 let stats = processor.statistics();
632 assert_eq!(stats.vertex_count, 4); }
634
635 #[test]
636 fn test_edge_with_new_vertices() {
637 let graph = create_test_graph().unwrap();
638 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
639 processor.set_batch_size(1); processor.add_edge("X".to_string(), "Y".to_string(), 10.0).unwrap();
643
644 let stats = processor.statistics();
645 assert_eq!(stats.vertex_count, 5); assert_eq!(stats.edge_count, 3); assert!(processor.vertex_cache.contains_key("X"));
649 assert!(processor.vertex_cache.contains_key("Y"));
650 assert_eq!(
651 processor.edge_cache.get(&("X".to_string(), "Y".to_string())),
652 Some(&10.0)
653 );
654 }
655}