use fsqlite_core::connection::{Connection, WriteMergeMode};
fn run_workload(conn: &Connection, commits: usize) -> i64 {
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS kv (k INTEGER PRIMARY KEY, v INTEGER NOT NULL);",
)
.unwrap();
for i in 0..commits {
let k = i + 1;
let v = ((i * 7 + 3) % 997) as i64;
conn.execute_batch(&format!(
"BEGIN CONCURRENT; INSERT OR REPLACE INTO kv(k, v) VALUES ({k}, {v}); COMMIT;"
))
.unwrap();
}
let stmt = conn.prepare("SELECT COALESCE(SUM(v), 0) FROM kv").unwrap();
let row = stmt.query_row().unwrap();
match &row.values()[0] {
fsqlite_types::SqliteValue::Integer(n) => *n,
other => panic!("expected integer sum, got {other:?}"),
}
}
#[test]
fn default_mode_is_safe_and_gate_locked() {
let conn = Connection::open(":memory:").unwrap();
assert_eq!(conn.write_merge_mode(), WriteMergeMode::Safe);
for h in 0..1024u64 {
assert!(
!conn.should_skip_ssi_validation(h),
"gate should be locked under SAFE at h={h}"
);
}
}
#[test]
fn lab_unsafe_pragma_activates_and_reports() {
let conn = Connection::open(":memory:").unwrap();
conn.execute_batch("PRAGMA fsqlite.write_merge = LAB_UNSAFE;")
.unwrap();
assert_eq!(conn.write_merge_mode(), WriteMergeMode::LabUnsafe);
assert_eq!(conn.write_merge_mode(), WriteMergeMode::LabUnsafe);
conn.execute_batch("PRAGMA fsqlite.write_merge = SAFE;")
.unwrap();
assert_eq!(conn.write_merge_mode(), WriteMergeMode::Safe);
}
#[test]
fn unknown_write_merge_value_errors() {
let conn = Connection::open(":memory:").unwrap();
let err = conn.execute_batch("PRAGMA fsqlite.write_merge = RECKLESS;");
assert!(err.is_err(), "unknown write_merge value must error");
assert_eq!(conn.write_merge_mode(), WriteMergeMode::Safe);
}
#[test]
fn alpha_pragma_round_trips() {
let conn = Connection::open(":memory:").unwrap();
conn.execute_batch("PRAGMA fsqlite.ssi_e_process_alpha = 0.005;")
.unwrap();
let snap = conn.ssi_e_process_snapshot();
assert!(
(snap.threshold - 200.0).abs() < 1e-9,
"threshold={} expected 200",
snap.threshold
);
assert!(
conn.execute_batch("PRAGMA fsqlite.ssi_e_process_alpha = 1.5;")
.is_err()
);
assert!(
conn.execute_batch("PRAGMA fsqlite.ssi_e_process_alpha = -0.1;")
.is_err()
);
}
#[test]
fn lab_unsafe_gate_opens_after_clean_history() {
let conn = Connection::open(":memory:").unwrap();
conn.execute_batch(
"PRAGMA fsqlite.write_merge = LAB_UNSAFE;
PRAGMA fsqlite.ssi_e_process_alpha = 0.001;",
)
.unwrap();
assert!(!conn.should_skip_ssi_validation(1));
for _ in 0..128 {
conn.observe_ssi_outcome(false);
}
let mut any_granted = false;
for h in (1..200u64).step_by(2) {
if conn.should_skip_ssi_validation(h) {
any_granted = true;
break;
}
}
assert!(
any_granted,
"gate should grant at least one skip after 128 clean observations"
);
let snap = conn.ssi_e_process_snapshot();
assert!(
snap.skip_grants > 0,
"skip_grants should be > 0 after gate opens"
);
}
#[test]
fn lab_unsafe_gate_closes_on_alert() {
let conn = Connection::open(":memory:").unwrap();
conn.execute_batch(
"PRAGMA fsqlite.write_merge = LAB_UNSAFE;
PRAGMA fsqlite.ssi_e_process_alpha = 0.001;",
)
.unwrap();
for _ in 0..64 {
conn.observe_ssi_outcome(false);
}
for _ in 0..5 {
conn.observe_ssi_outcome(true);
}
let snap = conn.ssi_e_process_snapshot();
assert_eq!(
snap.alert_state,
fsqlite_mvcc::GateAlertState::Alert,
"gate should be in Alert state after 5 conflicts, snapshot={snap}"
);
for h in 0..100u64 {
assert!(
!conn.should_skip_ssi_validation(h),
"gate must not grant a skip while in Alert; h={h} snap={snap}"
);
}
}
#[test]
fn lab_unsafe_commits_match_safe_commits() {
let safe_sum = {
let conn = Connection::open(":memory:").unwrap();
conn.execute_batch("PRAGMA fsqlite.write_merge = SAFE;")
.unwrap();
run_workload(&conn, 256)
};
let lab_sum = {
let conn = Connection::open(":memory:").unwrap();
conn.execute_batch(
"PRAGMA fsqlite.write_merge = LAB_UNSAFE;
PRAGMA fsqlite.ssi_e_process_alpha = 0.001;",
)
.unwrap();
for _ in 0..128 {
conn.observe_ssi_outcome(false);
}
run_workload(&conn, 256)
};
assert_eq!(
safe_sum, lab_sum,
"LAB_UNSAFE must produce identical final state as SAFE on a pivot-free workload"
);
}
#[test]
fn reset_gate_via_api_clears_state() {
let conn = Connection::open(":memory:").unwrap();
conn.execute_batch("PRAGMA fsqlite.write_merge = LAB_UNSAFE;")
.unwrap();
for _ in 0..32 {
conn.observe_ssi_outcome(false);
}
conn.observe_ssi_outcome(true);
let pre = conn.ssi_e_process_snapshot();
assert!(pre.observations > 0);
conn.reset_ssi_e_process_gate();
let post = conn.ssi_e_process_snapshot();
assert_eq!(post.observations, 0);
assert!((post.e_value - 1.0).abs() < 1e-12);
}