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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//! Regression test for ACID bug: public APIs could observe uncommitted state
//!
//! This test reproduces the original bug where read operations could see
//! uncommitted data because they bypassed the transaction system.
//!
//! **Hard Rule:** No API may observe state not bound to a committed snapshot_id.
//! If a value cannot be tied to a committed snapshot → it does not exist.
//!
//! # Implementation Status
//!
//! - ✅ **Phase 38-03 COMPLETE**: GraphBackend trait updated with snapshot_id parameters
//! - ⏳ **Phase 38-04 PENDING**: WAL filtering not yet implemented
//! - ❌ **Native/SQLite implementations**: Need to be updated to match trait signature
//!
//! # Current Blocker
//!
//! The GraphBackend trait has been updated with snapshot_id parameters, but the
//! NativeGraphBackend and SqliteGraphBackend implementations haven't been updated yet.
//! This causes compilation errors:
//!
//! ```text
//! error[E0050]: method `get_node` has 2 parameters but the declaration in trait `GraphBackend::get_node` has 3
//! ```
//!
//! Once 38-04 (WAL filtering) is complete and implementations are updated, these tests
//! should compile and verify snapshot isolation works correctly.
use SnapshotId;
// Note: These tests verify SnapshotId type functionality (38-02 - COMPLETE).
// Full integration tests are blocked until implementations match trait signature (38-04).
// ============================================================================
// SnapshotId Type Tests (Phase 38-02 - COMPLETE)
// ============================================================================
/// Verify SnapshotId::current() returns a valid snapshot
///
/// Current implementation returns SnapshotId(0) to indicate "all committed data".
/// Future enhancement will track max committed transaction ID from WAL.
/// Verify SnapshotId can be created from explicit transaction ID
///
/// This is used when you want to read from a specific historical snapshot.
/// Verify invalid sentinel value works for error cases
///
/// Used to indicate "no valid snapshot exists" in error paths.
/// Verify SnapshotId implements Copy, Clone, Hash, Eq, PartialEq
///
/// These traits are required for snapshot_id to be used as:
/// - Hash map keys (caching layers)
/// - Copyable parameters (API ergonomics)
/// - Comparable values (snapshot ordering)
// ============================================================================
// Regression Test Specifications (Blocked until 38-04 implementation complete)
// ============================================================================
// Note: The following test specifications are documented but commented out
// because the GraphBackend implementations haven't been updated to match
// the new trait signature (with snapshot_id parameters).
//
// Once 38-04 is complete, uncomment these tests to verify snapshot isolation.
/*
/// **REGRESSION TEST**: Original bug - public APIs could observe uncommitted state
///
/// # Expected Behavior (after 38-04 implementation)
///
/// 1. Create graph
/// 2. Take snapshot_1 before any writes
/// 3. Insert node (commits to snapshot_2)
/// 4. Read with snapshot_1 → should NOT see the new node
/// 5. Read with snapshot_2 → should see the new node
///
/// # Implementation Required
///
/// - [ ] GraphBackend implementations updated to match trait signature
/// - [ ] WAL filtering filters records by tx_id <= snapshot_id
/// - [ ] open_graph() API provides GraphBackend wrapper
#[test]
fn test_no_uncommitted_reads_via_public_api() {
use sqlitegraph::{open_graph, GraphConfig, BackendKind, snapshot::SnapshotId};
use tempfile::NamedTempFile;
let temp = NamedTempFile::new().unwrap();
let cfg = GraphConfig::native();
let graph = open_graph(temp.path(), &cfg).unwrap();
// Get snapshot BEFORE any writes
let snapshot_before = SnapshotId::current();
// Insert node (should auto-commit and create new snapshot)
let _node_id = graph.insert_node(NodeSpec {
kind: "test".to_string(),
name: "uncommitted".to_string(),
data: None,
}).unwrap();
// Get snapshot AFTER commit
let snapshot_after = SnapshotId::current();
// Read with snapshot_before should NOT see uncommitted node
let result_before = graph.get_node(snapshot_before, 1);
assert!(result_before.is_err(), "snapshot_before should not see node inserted after it");
// Read with snapshot_after should see committed data
let result_after = graph.get_node(snapshot_after, 1);
assert!(result_after.is_ok(), "snapshot_after should see committed data");
assert_eq!(result_after.unwrap().name, "uncommitted");
}
/// **REGRESSION TEST**: Committed data visible after snapshot
///
/// # Expected Behavior
///
/// Verifies the basic snapshot isolation guarantee:
/// - Snapshots taken before a commit don't see that commit
/// - Snapshots taken after a commit do see that commit
#[test]
fn test_committed_data_visible_after_snapshot() {
use sqlitegraph::{open_graph, GraphConfig, BackendKind, snapshot::SnapshotId};
use tempfile::NamedTempFile;
let temp = NamedTempFile::new().unwrap();
let cfg = GraphConfig::native();
let graph = open_graph(temp.path(), &cfg).unwrap();
// Insert and commit
let snapshot_before = SnapshotId::current();
let node_id = graph.insert_node(NodeSpec {
kind: "test".to_string(),
name: "committed".to_string(),
data: None,
}).unwrap();
let snapshot_after = SnapshotId::current();
// Read with snapshot_before should NOT see the node
let result_before = graph.get_node(snapshot_before, node_id);
assert!(result_before.is_err(), "snapshot_before should not see node inserted after it");
// Read with snapshot_after should see committed data
let result_after = graph.get_node(snapshot_after, node_id);
assert!(result_after.is_ok(), "snapshot_after should see committed data");
assert_eq!(result_after.unwrap().name, "committed");
}
*/