use ic_sqlite_vfs::db::migrate::Migration;
use ic_sqlite_vfs::sqlite_vfs::{lock, stable_blob};
use ic_sqlite_vfs::stable::memory;
use ic_sqlite_vfs::stable::meta::Superblock;
use ic_sqlite_vfs::{params, Db};
use serial_test::serial;
fn reset() {
stable_blob::invalidate_read_cache();
memory::reset_for_tests();
lock::reset_for_tests();
Db::init(memory::memory_for_tests()).unwrap();
}
#[test]
#[serial]
fn committed_update_marks_checksum_stale_until_refresh() {
reset();
Db::migrate(&[Migration {
version: 1,
sql: "CREATE TABLE stale(k TEXT PRIMARY KEY, v TEXT NOT NULL);",
}])
.unwrap();
Db::refresh_checksum().unwrap();
let before = Superblock::load().unwrap();
assert!(!before.is_checksum_stale());
Db::update(|connection| connection.execute_batch("INSERT INTO stale(k, v) VALUES ('k', 'v')"))
.unwrap();
let after_update = Superblock::load().unwrap();
assert!(after_update.is_checksum_stale());
assert_eq!(after_update.last_tx_id, before.last_tx_id + 1);
assert_eq!(after_update.checksum, before.checksum);
let current_checksum = Db::db_checksum().unwrap();
assert!(Superblock::load().unwrap().is_checksum_stale());
let refreshed = Db::refresh_checksum().unwrap();
let after_refresh = Superblock::load().unwrap();
assert_eq!(refreshed, current_checksum);
assert_eq!(after_refresh.checksum, current_checksum);
assert!(!after_refresh.is_checksum_stale());
}
#[test]
#[serial]
fn chunked_checksum_refresh_updates_metadata_only_when_complete() {
reset();
Db::migrate(&[Migration {
version: 1,
sql: "CREATE TABLE chunked(k TEXT PRIMARY KEY, v TEXT NOT NULL);",
}])
.unwrap();
Db::refresh_checksum().unwrap();
let before = Superblock::load().unwrap();
Db::update(|connection| {
let mut statement = connection.prepare("INSERT INTO chunked(k, v) VALUES (?1, ?2)")?;
for index in 0..32 {
let key = format!("k{index}");
let value = format!("value-{index}");
statement.execute(params![key, value])?;
}
Ok(())
})
.unwrap();
let first = Db::refresh_checksum_chunk(64).unwrap();
assert!(!first.complete);
assert_eq!(first.scanned_bytes, 64);
let partial = Superblock::load().unwrap();
assert!(partial.is_checksum_stale());
assert!(partial.is_checksum_refreshing());
assert_eq!(partial.checksum, before.checksum);
let mut latest = first;
while !latest.complete {
latest = Db::refresh_checksum_chunk(64).unwrap();
}
let after = Superblock::load().unwrap();
assert_eq!(after.checksum, Db::db_checksum().unwrap());
assert_eq!(latest.checksum, after.checksum);
assert_eq!(latest.scanned_bytes, latest.db_size);
assert!(!after.is_checksum_stale());
assert!(!after.is_checksum_refreshing());
}
#[test]
#[serial]
fn committed_update_clears_partial_checksum_refresh() {
reset();
Db::migrate(&[Migration {
version: 1,
sql: "CREATE TABLE refresh_reset(k TEXT PRIMARY KEY, v TEXT NOT NULL);",
}])
.unwrap();
Db::update(|connection| {
connection.execute_batch("INSERT INTO refresh_reset(k, v) VALUES ('a', 'b')")
})
.unwrap();
let first = Db::refresh_checksum_chunk(64).unwrap();
assert!(!first.complete);
assert!(Superblock::load().unwrap().is_checksum_refreshing());
Db::update(|connection| {
connection.execute_batch("INSERT INTO refresh_reset(k, v) VALUES ('c', 'd')")
})
.unwrap();
let after_update = Superblock::load().unwrap();
assert!(after_update.is_checksum_stale());
assert!(!after_update.is_checksum_refreshing());
assert_eq!(after_update.checksum_refresh_offset, 0);
}
#[test]
#[serial]
fn checksum_refresh_rejects_empty_chunk_size() {
reset();
Db::migrate(&[Migration {
version: 1,
sql: "CREATE TABLE checksum_limit(id INTEGER PRIMARY KEY);",
}])
.unwrap();
assert!(Db::refresh_checksum_chunk(0).is_err());
}
#[test]
#[serial]
fn noop_update_does_not_mark_checksum_stale_or_advance_tx_id() {
reset();
Db::migrate(&[Migration {
version: 1,
sql: "CREATE TABLE noop_update(id INTEGER PRIMARY KEY);",
}])
.unwrap();
Db::refresh_checksum().unwrap();
let before = Superblock::load().unwrap();
Db::update(|_connection| Ok(())).unwrap();
let after = Superblock::load().unwrap();
assert_eq!(after.db_size, before.db_size);
assert_eq!(after.last_tx_id, before.last_tx_id);
assert_eq!(after.checksum, before.checksum);
assert_eq!(after.is_checksum_stale(), before.is_checksum_stale());
}