Skip to main content

engram_core/migrations/
mod.rs

1//! Idempotent startup migrations gated by `schema_meta(key, value)`.
2
3pub mod embedding_model_v1;
4pub mod tags_format_v1;
5
6use engram_storage::Database;
7
8use crate::error::CoreError;
9
10pub use embedding_model_v1::EMBEDDING_MODEL_KEY;
11pub use tags_format_v1::{TAGS_FORMAT_KEY, TAGS_FORMAT_TARGET_VALUE, TagsFormatV1Stats};
12
13const ENV_DRY_RUN: &str = "ENGRAM_MIGRATIONS_DRY_RUN";
14const ENV_TAGS_STRICT: &str = "ENGRAM_TAGS_MIGRATION_STRICT";
15
16#[derive(Debug, Default, Clone, PartialEq, Eq)]
17pub struct MigrationReport {
18    pub tags_format_v1: Option<TagsFormatV1Stats>,
19}
20
21pub fn run_pending(database: &Database) -> Result<MigrationReport, CoreError> {
22    let dry_run = std::env::var(ENV_DRY_RUN).is_ok();
23    let strict = std::env::var(ENV_TAGS_STRICT).is_ok();
24    let tags_format_v1 = tags_format_v1::run(database, dry_run, strict)?;
25    Ok(MigrationReport { tags_format_v1 })
26}
27
28pub(crate) fn read_meta(database: &Database, key: &str) -> Result<Option<String>, CoreError> {
29    use rusqlite::OptionalExtension;
30    database
31        .connection()
32        .query_row(
33            "SELECT value FROM schema_meta WHERE key = ?1",
34            [key],
35            |row| row.get::<_, String>(0),
36        )
37        .optional()
38        .map_err(|error| CoreError::MigrationFailed(format!("schema_meta read: {error}")))
39}
40
41pub(crate) fn write_meta(database: &Database, key: &str, value: &str) -> Result<(), CoreError> {
42    database
43        .connection()
44        .execute(
45            "INSERT OR REPLACE INTO schema_meta(key, value) VALUES (?1, ?2)",
46            [key, value],
47        )
48        .map_err(|error| CoreError::MigrationFailed(format!("schema_meta write: {error}")))?;
49    Ok(())
50}