Skip to main content

graphrag_core/graph/incremental/
mod.rs

1//! Comprehensive incremental updates architecture for GraphRAG-RS
2//!
3//! This module provides zero-downtime incremental updates with ACID-like guarantees,
4//! intelligent cache invalidation, conflict resolution, and comprehensive monitoring.
5//!
6//! ## Architecture Goals
7//!
8//! - **Zero-downtime updates**: System remains available during modifications
9//! - **Consistency guarantees**: ACID-like properties for graph operations
10//! - **Performance**: Updates should be 10x+ faster than full reconstruction
11//! - **Scalability**: Handle thousands of concurrent updates per second
12//! - **Observability**: Complete audit trail of all changes
13//!
14//! ## Key Components
15//!
16//! - `IncrementalGraphStore` trait for atomic update operations
17//! - `ChangeRecord` and `ChangeLog` for tracking modifications
18//! - `GraphDelta` for representing atomic change sets
19//! - `ConflictResolver` for handling concurrent modifications
20//! - `SelectiveInvalidation` for cache management
21//! - `UpdateMonitor` for change tracking and metrics
22//! - `IncrementalPageRank` for efficient graph algorithm updates
23//!
24//! Phase 4 file split: this previously 2905-LOC monolithic file is now a directory
25//! module with focused sub-files (`types`, `helpers`, `manager`, `store`). Public
26//! items are re-exported here so existing paths (`crate::graph::incremental::*`)
27//! resolve unchanged.
28
29mod helpers;
30mod manager;
31mod store;
32mod types;
33
34pub use helpers::*;
35pub use manager::*;
36pub use store::*;
37pub use types::*;
38
39// Imports surfaced for the inline `mod tests` below (which uses `super::*` to
40// glob-pull both the re-exported items above and these external types — the
41// same way the original monolithic file's tests resolved them).
42#[cfg(test)]
43#[allow(unused_imports)]
44use {
45    crate::core::{Entity, EntityId, KnowledgeGraph, Relationship},
46    chrono::Utc,
47    std::collections::{HashMap, HashSet},
48    std::sync::Arc,
49    std::time::Duration,
50};
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn test_update_id_generation() {
58        let id1 = UpdateId::new();
59        let id2 = UpdateId::new();
60        assert_ne!(id1.as_str(), id2.as_str());
61    }
62
63    #[test]
64    fn test_transaction_id_generation() {
65        let tx1 = TransactionId::new();
66        let tx2 = TransactionId::new();
67        assert_ne!(tx1.as_str(), tx2.as_str());
68    }
69
70    #[test]
71    fn test_change_record_creation() {
72        let entity = Entity::new(
73            EntityId::new("test".to_string()),
74            "Test Entity".to_string(),
75            "Person".to_string(),
76            0.9,
77        );
78
79        let config = IncrementalConfig::default();
80        let graph = KnowledgeGraph::new();
81        let manager = IncrementalGraphManager::new(graph, config);
82
83        let change = manager.create_change_record(
84            ChangeType::EntityAdded,
85            Operation::Insert,
86            ChangeData::Entity(entity.clone()),
87            Some(entity.id.clone()),
88            None,
89        );
90
91        assert_eq!(change.change_type, ChangeType::EntityAdded);
92        assert_eq!(change.operation, Operation::Insert);
93        assert_eq!(change.entity_id, Some(entity.id));
94    }
95
96    #[test]
97    fn test_conflict_resolver_creation() {
98        let resolver = ConflictResolver::new(ConflictStrategy::KeepExisting);
99        assert!(matches!(resolver.strategy, ConflictStrategy::KeepExisting));
100    }
101
102    #[test]
103    fn test_incremental_config_default() {
104        let config = IncrementalConfig::default();
105        assert_eq!(config.max_change_log_size, 10000);
106        assert_eq!(config.batch_size, 100);
107        assert!(config.enable_monitoring);
108    }
109
110    #[test]
111    fn test_statistics_creation() {
112        let stats = IncrementalStatistics::empty();
113        assert_eq!(stats.total_updates, 0);
114        assert_eq!(stats.entities_added, 0);
115        assert_eq!(stats.average_update_time_ms, 0.0);
116    }
117
118    #[cfg(feature = "incremental")]
119    #[cfg(feature = "incremental")]
120    #[test]
121    fn test_batch_processor_creation() {
122        let processor = BatchProcessor::new(100, Duration::from_millis(500), 10);
123        let metrics = processor.get_metrics();
124        assert_eq!(metrics.total_batches_processed, 0);
125    }
126
127    #[cfg(feature = "incremental")]
128    #[tokio::test]
129    async fn test_selective_invalidation() {
130        let invalidation = SelectiveInvalidation::new();
131
132        let region = CacheRegion {
133            region_id: "test_region".to_string(),
134            entity_ids: [EntityId::new("entity1".to_string())].into_iter().collect(),
135            relationship_types: ["KNOWS".to_string()].into_iter().collect(),
136            document_ids: HashSet::new(),
137            last_modified: Utc::now(),
138        };
139
140        invalidation.register_cache_region(region);
141
142        let entity = Entity::new(
143            EntityId::new("entity1".to_string()),
144            "Entity 1".to_string(),
145            "Person".to_string(),
146            0.9,
147        );
148
149        let ent_id_for_log = entity.id.clone();
150        let change = ChangeRecord {
151            change_id: UpdateId::new(),
152            timestamp: Utc::now(),
153            change_type: ChangeType::EntityUpdated,
154            entity_id: Some(ent_id_for_log),
155            document_id: None,
156            operation: Operation::Update,
157            data: ChangeData::Entity(entity),
158            metadata: HashMap::new(),
159        };
160
161        let strategies = invalidation.invalidate_for_changes(&[change]);
162        assert!(!strategies.is_empty());
163    }
164
165    #[cfg(feature = "incremental")]
166    #[test]
167    fn test_conflict_resolver_merge() {
168        let resolver = ConflictResolver::new(ConflictStrategy::Merge);
169
170        let entity1 = Entity::new(
171            EntityId::new("entity1".to_string()),
172            "Entity 1".to_string(),
173            "Person".to_string(),
174            0.8,
175        );
176
177        let entity2 = Entity::new(
178            EntityId::new("entity1".to_string()),
179            "Entity 1 Updated".to_string(),
180            "Person".to_string(),
181            0.9,
182        );
183
184        let merged = resolver.merge_entities(&entity1, &entity2).unwrap();
185        assert_eq!(merged.confidence, 0.9); // Should take higher confidence
186        assert_eq!(merged.name, "Entity 1 Updated");
187    }
188
189    #[test]
190    fn test_graph_statistics_creation() {
191        let stats = GraphStatistics {
192            node_count: 100,
193            edge_count: 150,
194            average_degree: 3.0,
195            max_degree: 10,
196            connected_components: 1,
197            clustering_coefficient: 0.3,
198            last_updated: Utc::now(),
199        };
200
201        assert_eq!(stats.node_count, 100);
202        assert_eq!(stats.edge_count, 150);
203    }
204
205    #[test]
206    fn test_consistency_report_creation() {
207        let report = ConsistencyReport {
208            is_consistent: true,
209            orphaned_entities: vec![],
210            broken_relationships: vec![],
211            missing_embeddings: vec![],
212            validation_time: Utc::now(),
213            issues_found: 0,
214        };
215
216        assert!(report.is_consistent);
217        assert_eq!(report.issues_found, 0);
218    }
219
220    #[test]
221    fn test_change_event_creation() {
222        let event = ChangeEvent {
223            event_id: UpdateId::new(),
224            event_type: ChangeEventType::EntityUpserted,
225            entity_id: Some(EntityId::new("entity1".to_string())),
226            timestamp: Utc::now(),
227            metadata: HashMap::new(),
228        };
229
230        assert!(matches!(event.event_type, ChangeEventType::EntityUpserted));
231        assert!(event.entity_id.is_some());
232    }
233}