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