sqlitegraph/graph/snapshot.rs
1//! Snapshot management functionality for SqliteGraph.
2
3use std::sync::Arc;
4
5use crate::errors::SqliteGraphError;
6
7use super::SqliteGraph;
8
9impl SqliteGraph {
10 /// Update snapshot with current cache state
11 /// This is called automatically after write operations
12 pub(crate) fn update_snapshot(&self) {
13 self.snapshot_manager.update_snapshot(
14 &self.outgoing_cache_ref().inner(),
15 &self.incoming_cache_ref().inner(),
16 );
17 }
18
19 /// Acquire a deterministic snapshot of the current graph state
20 ///
21 /// Returns a read-only snapshot that provides isolated access to graph data.
22 /// The snapshot contains cloned adjacency maps and uses a read-only SQLite connection.
23 ///
24 /// # MVCC-lite Snapshot Isolation
25 ///
26 /// Snapshots provide **MVCC-lite** isolation guarantees:
27 /// - **Immutable**: Snapshot state never changes after creation
28 /// - **Consistent**: Snapshot sees a point-in-time view of the graph
29 /// - **Isolated**: Snapshot unaffected by subsequent writes
30 /// - **Cloned Data**: Adjacency maps are fully cloned (not shared)
31 ///
32 /// # Cache Requirement
33 ///
34 /// **IMPORTANT**: Snapshots read from the in-memory adjacency cache, not the database.
35 /// For accurate snapshots, the cache must be warmed first:
36 ///
37 /// ```rust
38 /// use sqlitegraph::SqliteGraph;
39 ///
40 /// let graph = SqliteGraph::open_in_memory()?;
41 /// // ... perform writes ...
42 ///
43 /// // Warm cache before snapshot
44 /// let entity_ids = graph.list_entity_ids()?;
45 /// for &id in &entity_ids {
46 /// let _ = graph.query().outgoing(id);
47 /// let _ = graph.query().incoming(id);
48 /// }
49 ///
50 /// // Now acquire snapshot
51 /// let snapshot = graph.acquire_snapshot()?;
52 /// assert!(snapshot.node_count() > 0);
53 /// # Ok::<(), sqlitegraph::SqliteGraphError>(())
54 /// ```
55 ///
56 /// Without cache warming, snapshots may appear empty even if the database has data.
57 ///
58 /// # Thread Safety
59 ///
60 /// The underlying `SnapshotManager` is thread-safe and uses lock-free `ArcSwap`.
61 /// However, `SqliteGraph` itself is **NOT thread-safe** (contains `RefCell`, non-Sync types).
62 ///
63 /// For concurrent snapshot acquisition, wrap `SqliteGraph` in a `Mutex` or `RwLock`:
64 ///
65 /// ```rust
66 /// use std::sync::{Arc, Mutex};
67 /// use sqlitegraph::SqliteGraph;
68 ///
69 /// let graph = Arc::new(Mutex::new(SqliteGraph::open_in_memory()?));
70 /// // Multiple threads can now safely acquire snapshots
71 /// # Ok::<(), sqlitegraph::SqliteGraphError>(())
72 /// ```
73 ///
74 /// # Performance
75 ///
76 /// - **Acquisition**: < 1ms typical (Arc::clone overhead)
77 /// - **Memory**: O(N + E) where N = nodes, E = edges (full copy)
78 /// - **Throughput**: > 10,000 snapshots/second single-threaded
79 ///
80 /// # Returns
81 ///
82 /// Result containing `GraphSnapshot` or error
83 ///
84 /// # Errors
85 ///
86 /// Returns error if:
87 /// - Read-only SQLite connection cannot be opened
88 /// - Database connection fails
89 pub fn acquire_snapshot(&self) -> Result<crate::mvcc::GraphSnapshot, SqliteGraphError> {
90 // Update snapshot with current cache state
91 self.update_snapshot();
92
93 // Acquire snapshot state
94 let snapshot_state = self.snapshot_manager.acquire_snapshot();
95
96 // Use in-memory database for snapshot operations
97 let db_path = ":memory:";
98
99 crate::mvcc::GraphSnapshot::new(snapshot_state, db_path)
100 .map_err(|e| SqliteGraphError::connection(e.to_string()))
101 }
102
103 /// Convenience alias for `acquire_snapshot()`
104 ///
105 /// This is a shorter name for acquiring snapshots, equivalent to:
106 /// ```rust
107 /// # use sqlitegraph::SqliteGraph;
108 /// # let graph = unsafe { std::mem::zeroed() };
109 /// let snapshot = graph.snapshot()?;
110 /// ```
111 ///
112 /// See `acquire_snapshot()` for full documentation.
113 pub fn snapshot(&self) -> Result<crate::mvcc::GraphSnapshot, SqliteGraphError> {
114 self.acquire_snapshot()
115 }
116
117 /// Get the current snapshot state without creating a new connection
118 /// This is useful for internal operations and testing
119 pub(crate) fn current_snapshot_state(&self) -> Arc<crate::mvcc::SnapshotState> {
120 self.update_snapshot();
121 self.snapshot_manager.current_snapshot()
122 }
123
124 /// Get the number of nodes in the current snapshot
125 ///
126 /// **Note**: This requires cache warming to return accurate results.
127 /// See `acquire_snapshot()` documentation for details.
128 pub fn snapshot_node_count(&self) -> usize {
129 self.current_snapshot_state().node_count()
130 }
131
132 /// Get the number of edges in the current snapshot
133 ///
134 /// **Note**: This requires cache warming to return accurate results.
135 /// See `acquire_snapshot()` documentation for details.
136 pub fn snapshot_edge_count(&self) -> usize {
137 self.current_snapshot_state().edge_count()
138 }
139
140 /// Check if a node exists in the current snapshot
141 ///
142 /// **Note**: This requires cache warming to return accurate results.
143 /// See `acquire_snapshot()` documentation for details.
144 pub fn snapshot_contains_node(&self, node_id: i64) -> bool {
145 self.current_snapshot_state().contains_node(node_id)
146 }
147}