use anyhow::Result;
use chrono::Utc;
use rusqlite::Connection;
use serde_json::Value;
pub(crate) fn json_to_string(v: &Value) -> Result<String> {
serde_json::to_string(v).map_err(|e| anyhow::anyhow!("JSON serialization failed: {}", e))
}
pub mod evidence;
pub mod execution;
pub mod hook_compat;
pub mod knowledge;
pub mod planning;
type Migration = fn(&rusqlite::Transaction<'_>) -> Result<()>;
const MIGRATIONS: &[(u32, &str, Migration)] = &[
(1, "execution-domain", execution::migrate_v1_execution),
(2, "planning-domain", planning::migrate_v2_planning),
(3, "knowledge-domain", knowledge::migrate_v3_knowledge),
(4, "evidence-domain", evidence::migrate_v4_evidence),
(5, "hook-compat", hook_compat::migrate_v5_hook_compat),
];
pub(crate) fn run_migrations(conn: &Connection) -> Result<()> {
ensure_version_table(conn)?;
let applied = applied_versions(conn)?;
for (version, name, migrate) in MIGRATIONS {
if applied.contains(version) {
continue;
}
let tx = conn.unchecked_transaction()?;
migrate(&tx)
.map_err(|e| anyhow::anyhow!("migration {} ({}) failed: {}", version, name, e))?;
tx.execute(
"INSERT INTO atheneum_schema_version (version, applied_at) VALUES (?1, ?2)",
rusqlite::params![*version as i64, Utc::now().to_rfc3339()],
)?;
tx.commit()?;
}
Ok(())
}
fn ensure_version_table(conn: &Connection) -> Result<()> {
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS atheneum_schema_version (
version INTEGER PRIMARY KEY NOT NULL,
applied_at TEXT NOT NULL
);",
)?;
Ok(())
}
fn applied_versions(conn: &Connection) -> Result<std::collections::HashSet<u32>> {
let mut stmt = conn.prepare("SELECT version FROM atheneum_schema_version")?;
let rows = stmt.query_map([], |row| row.get::<_, i64>(0))?;
let mut out = std::collections::HashSet::new();
for row in rows {
out.insert(row? as u32);
}
Ok(out)
}
pub(crate) fn stamp_sql_id(
tx: &rusqlite::Transaction<'_>,
entity_id: i64,
existing_data: &Value,
sql_id: i64,
) -> Result<()> {
let mut data = existing_data.clone();
if let Some(obj) = data.as_object_mut() {
obj.insert("sql_id".to_string(), Value::Number(sql_id.into()));
}
tx.execute(
"UPDATE graph_entities SET data = ?1 WHERE id = ?2",
rusqlite::params![serde_json::to_string(&data)?, entity_id],
)?;
Ok(())
}