grit-core 0.1.0

Embedded, bi-temporal property graph for agent memory: one SQLite file, in-process, deterministic
Documentation
//! Forward-only schema migrations via `PRAGMA user_version`
//! (Design Invariant 8).

use rusqlite::Connection;

use crate::error::{Error, Result};

/// The schema version this build reads and writes.
pub const SCHEMA_VERSION: i64 = 1;

const SCHEMA_V1: &str = include_str!("schema.sql");

/// Bring `conn`'s schema up to [`SCHEMA_VERSION`]. A brand-new database gets
/// the full latest schema; older versions are migrated step by step; a newer
/// version is refused (migrations are forward-only, never down).
pub(crate) fn migrate(conn: &mut Connection) -> Result<()> {
    loop {
        let version: i64 = conn.query_row("PRAGMA user_version", [], |r| r.get(0))?;
        match version {
            SCHEMA_VERSION => return Ok(()),
            v if v > SCHEMA_VERSION => {
                return Err(Error::SchemaTooNew {
                    found: v,
                    supported: SCHEMA_VERSION,
                });
            }
            0 => apply_step(conn, SCHEMA_V1, 1)?,
            // Future: 1 => apply_step(conn, MIGRATE_1_TO_2, 2)?,
            v => unreachable!("no migration path from version {v}"),
        }
    }
}

fn apply_step(conn: &mut Connection, sql: &str, target: i64) -> Result<()> {
    let tx = conn.transaction()?;
    tx.execute_batch(sql)?;
    // PRAGMA can't take bound parameters; target is a compile-time constant.
    tx.pragma_update(None, "user_version", target)?;
    tx.commit()?;
    Ok(())
}