pub mod gate;
pub mod import;
mod migrate;
pub mod status;
use anyhow::{Context, Result};
use rusqlite::Connection;
use std::path::{Path, PathBuf};
const SCHEMA_DB_FILENAME: &str = "schema.sqlite";
pub fn default_path() -> PathBuf {
crate::db::data_dir().join(SCHEMA_DB_FILENAME)
}
pub fn open_at(path: &Path) -> Result<Connection> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("create schema db parent {}", parent.display()))?;
}
let conn =
Connection::open(path).with_context(|| format!("open schema db at {}", path.display()))?;
conn.execute_batch(
"PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;",
)?;
migrate::run_migrations(&conn)?;
Ok(conn)
}
pub fn open() -> Result<Connection> {
open_at(&default_path())
}
pub fn reset_at(path: &Path) -> Result<()> {
let _ = std::fs::remove_file(path);
let _ = std::fs::remove_file(path.with_extension("sqlite-wal"));
let _ = std::fs::remove_file(path.with_extension("sqlite-shm"));
let _ = open_at(path)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::test_support::{cleanup_temp_db_files as cleanup, unique_temp_db_path};
fn unique_temp_path() -> PathBuf {
unique_temp_db_path("schema-db")
}
#[test]
fn open_creates_file_and_applies_schema_baseline() {
let path = unique_temp_path();
let conn = open_at(&path).expect("open schema db");
assert!(path.exists(), "db file should exist at {}", path.display());
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='hosts'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(count, 1);
cleanup(&path);
}
#[test]
fn reopen_is_idempotent() {
let path = unique_temp_path();
{
let _c = open_at(&path).expect("first open");
}
let c = open_at(&path).expect("second open");
let host_count: i64 = c
.query_row("SELECT COUNT(*) FROM hosts", [], |row| row.get(0))
.unwrap();
assert_eq!(host_count, 2, "seed must not duplicate on re-open");
cleanup(&path);
}
#[test]
fn open_succeeds_after_dir_already_exists() {
let path = unique_temp_path();
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let _c = open_at(&path).expect("open should not fail when dir exists");
cleanup(&path);
}
#[test]
fn reset_drops_existing_schema_data_and_reseeds() {
let path = unique_temp_path();
{
let conn = open_at(&path).unwrap();
conn.execute(
"INSERT INTO hosts(name, enabled, created_at_epoch) VALUES ('extra', 1, 0)",
[],
)
.unwrap();
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM hosts", [], |row| row.get(0))
.unwrap();
assert_eq!(count, 3, "seeded 2 + extra 1");
}
reset_at(&path).expect("reset");
let conn = open_at(&path).unwrap();
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM hosts", [], |row| row.get(0))
.unwrap();
assert_eq!(count, 2, "after reset only the 2 seed rows remain");
cleanup(&path);
}
}