Skip to main content

ic_sqlite_vfs/db/
migrate.rs

1//! Minimal schema migration runner.
2//!
3//! Versions are stored both in SQLite and the stable superblock. The SQLite table
4//! is the source of truth for applied SQL; the superblock is quick canister state.
5
6use crate::db::connection::Connection;
7use crate::db::DbError;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub struct Migration {
11    pub version: u64,
12    pub sql: &'static str,
13}
14
15pub fn apply(connection: &Connection, migrations: &[Migration]) -> Result<(), DbError> {
16    connection.execute_batch(
17        "CREATE TABLE IF NOT EXISTS __ic_sqlite_migrations (
18            version INTEGER PRIMARY KEY NOT NULL
19        )",
20    )?;
21
22    for migration in migrations {
23        let version = sqlite_version(migration.version)?;
24        let exists = connection.query_i64(&format!(
25            "SELECT EXISTS(SELECT 1 FROM __ic_sqlite_migrations WHERE version = {version})"
26        ))?;
27        if exists != 0 {
28            continue;
29        }
30        connection.execute_batch(migration.sql)?;
31        connection.execute_batch(&format!(
32            "INSERT INTO __ic_sqlite_migrations(version) VALUES ({version})"
33        ))?;
34    }
35
36    Ok(())
37}
38
39fn sqlite_version(version: u64) -> Result<i64, DbError> {
40    i64::try_from(version).map_err(|_| DbError::MigrationVersionOutOfRange(version))
41}