use fathomdb_schema::{check_migration_accretion, migrate_with_steps, MIGRATIONS, SCHEMA_VERSION};
use rusqlite::Connection;
use std::sync::Once;
fn register_sqlite_vec_once() {
static REGISTER: Once = Once::new();
REGISTER.call_once(|| unsafe {
let entrypoint: unsafe extern "C" fn(
*mut rusqlite::ffi::sqlite3,
*mut *const std::os::raw::c_char,
*const rusqlite::ffi::sqlite3_api_routines,
) -> std::os::raw::c_int = std::mem::transmute(sqlite_vec::sqlite3_vec_init as *const ());
rusqlite::ffi::sqlite3_auto_extension(Some(entrypoint));
});
}
fn user_version(conn: &Connection) -> u32 {
conn.query_row("PRAGMA user_version", [], |row| row.get::<_, u32>(0)).unwrap()
}
fn set_user_version(conn: &Connection, version: u32) {
conn.pragma_update(None, "user_version", version).unwrap();
}
fn column_names(conn: &Connection, table: &str) -> Vec<String> {
conn.prepare(&format!("PRAGMA table_info({table})"))
.unwrap()
.query_map([], |row| row.get::<_, String>(1))
.unwrap()
.filter_map(Result::ok)
.collect()
}
#[test]
fn s18_importance_column_present_and_schema_version_is_18() {
register_sqlite_vec_once();
let conn = Connection::open_in_memory().unwrap();
set_user_version(&conn, 1);
migrate_with_steps(&conn, MIGRATIONS).expect("migration must succeed");
let cols = column_names(&conn, "canonical_nodes");
assert!(
cols.contains(&"importance".to_string()),
"canonical_nodes must have importance after step-18, got: {cols:?}"
);
assert_eq!(user_version(&conn), SCHEMA_VERSION);
assert_eq!(
SCHEMA_VERSION, 24,
"SCHEMA_VERSION must be 24 (step-24 projection-registry EAV + property-FTS, Slice 15d)"
);
assert!(
MIGRATIONS.iter().any(|m| m.step_id == 18),
"step-18 (F9 importance) must still be present in the migration set"
);
}
#[test]
fn s18_forward_only_from_v17_importance_null_and_no_vector_rewrite() {
register_sqlite_vec_once();
let conn = Connection::open_in_memory().unwrap();
set_user_version(&conn, 1);
let steps_to_17: Vec<_> = MIGRATIONS.iter().filter(|m| m.step_id <= 17).cloned().collect();
migrate_with_steps(&conn, &steps_to_17).expect("migrate to v17");
assert_eq!(user_version(&conn), 17, "precondition: DB is at the old schema version 17");
conn.execute(
"INSERT INTO canonical_nodes(write_cursor, kind, body, row_kind)
VALUES(1, 'note', 'legacy body about widgets', 'leaf')",
[],
)
.expect("legacy node insert at v17");
conn.execute(
"INSERT INTO _fathomdb_vector_rows(rowid, kind, write_cursor) VALUES(1, 'note', 1)",
[],
)
.expect("seed vector-row bookkeeping");
let vrows_before: i64 =
conn.query_row("SELECT COUNT(*) FROM _fathomdb_vector_rows", [], |r| r.get(0)).unwrap();
let step18_only: Vec<_> = MIGRATIONS.iter().filter(|m| m.step_id == 18).cloned().collect();
let report = migrate_with_steps(&conn, &step18_only).expect("forward migrate to v18");
assert_eq!(report.schema_version_before, 17);
assert_eq!(report.schema_version_after, 18);
let ran: Vec<u32> = report.migration_steps.iter().map(|s| s.step_id).collect();
assert_eq!(ran, vec![18], "only step-18 must run when starting from v17");
assert_eq!(user_version(&conn), 18);
let importance: Option<f64> = conn
.query_row("SELECT importance FROM canonical_nodes WHERE write_cursor = 1", [], |r| {
r.get(0)
})
.expect("legacy row must be readable after step-18");
assert!(importance.is_none(), "legacy importance must back-fill to NULL (graceful-absent)");
let vrows_after: i64 =
conn.query_row("SELECT COUNT(*) FROM _fathomdb_vector_rows", [], |r| r.get(0)).unwrap();
assert_eq!(vrows_before, vrows_after, "step-18 must NOT rewrite the vector corpus");
assert_eq!(vrows_after, 1, "the seeded vector row must survive step-18 untouched");
}
#[test]
fn s18_importance_three_way_sentinel_roundtrips() {
register_sqlite_vec_once();
let conn = Connection::open_in_memory().unwrap();
set_user_version(&conn, 1);
migrate_with_steps(&conn, MIGRATIONS).expect("migrate to head");
conn.execute(
"INSERT INTO canonical_nodes(write_cursor, kind, body, row_kind, importance)
VALUES(1, 'note', 'absent', 'leaf', NULL),
(2, 'note', 'floor', 'leaf', 0.0),
(3, 'note', 'high', 'leaf', 1.0)",
[],
)
.expect("insert 3-way sentinel rows");
let read = |wc: i64| -> Option<f64> {
conn.query_row(
"SELECT importance FROM canonical_nodes WHERE write_cursor = ?1",
[wc],
|r| r.get(0),
)
.unwrap()
};
assert!(read(1).is_none(), "NULL = never assigned (graceful-absent)");
assert_eq!(read(2), Some(0.0), "0.0 = explicit floor/de-weight");
assert_eq!(read(3), Some(1.0), "1.0 = explicit importance");
}
#[test]
fn s18_passes_accretion_guard_only_with_marker() {
let step18 =
MIGRATIONS.iter().find(|m| m.step_id == 18).expect("step 18 (F9 importance) must exist");
check_migration_accretion("018_f9_importance.sql", step18.sql)
.expect("step 18 must pass the accretion guard with its exemption marker");
let without_marker: String = step18
.sql
.lines()
.filter(|line| !line.contains("MIGRATION-ACCRETION-EXEMPTION"))
.collect::<Vec<_>>()
.join("\n");
check_migration_accretion("018_f9_importance.sql", &without_marker)
.expect_err("step 18 without the exemption marker must be rejected");
}