Skip to main content

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}